BIND 10 trac782, updated. fe21d5a407b1f779c55db60d4760ecec477c921e aaa

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Jun 7 08:42:25 UTC 2011


The branch, trac782 has been updated
       via  fe21d5a407b1f779c55db60d4760ecec477c921e (commit)
       via  a251c4f239e7b42856314412142cd9777f91dbf1 (commit)
       via  2360fc72c24eb39d32b0afd6a389cfe8f10cb2f9 (commit)
       via  fc9c42d22de8c2c5555573a1a3e29b2d30146a29 (commit)
       via  3303478b6f9943fd5514268bb1c0c42a638d107a (commit)
       via  d158640b970e5d8f0e5d4f8c6c278f03ee0e47e7 (commit)
       via  d11b5e89203a5340d4e5ca51c4c02db17c33dc1f (commit)
       via  de0694d2f4c7dba909eb922e3fa1a1269d3cbc78 (commit)
       via  bc0688b0e7846ec9bb38e4e014ed23be84876948 (commit)
       via  b8a7bf58c974c9ba4518fd894963cd66a19baf7e (commit)
       via  3a54f64afc94e9394a527872b957609d14002ff1 (commit)
       via  7c390c1149baab7bd33e741afcdefb827275d5cd (commit)
       via  62168918aceee04418765089cdc5fdfb34bf66a3 (commit)
       via  929a9cae2b351e67ac1953514a63b0c54095361c (commit)
       via  04ea273e2988cc405ae3ee7555a0f028258c17a5 (commit)
       via  d1b5154a7c17058ff49aa67f389f52496228e4b6 (commit)
       via  5b495e880e559c413a80d0dcc741a5076f3f7eb4 (commit)
       via  7c576f2e3d986b0f58883776822323ff57535a3c (commit)
       via  201bae76d11706e9fe9c09491ed216c225a02e9d (commit)
       via  be4334aa0b66fd01f9b3f859c309545459dfab00 (commit)
       via  1440e71559ad3565dff46e9cac31f31e6748d8f2 (commit)
       via  a407cb3d58c78b93afe294be13e7b360b11fd542 (commit)
       via  1cccbc6845d795f8d8e2b0c7ba637fb28e179e71 (commit)
       via  0c81e90348e53e673a05e92e150cec0f598a2d4e (commit)
       via  882cc4d6b2b9f391e72fdc0bf8eb82bdb846ca61 (commit)
       via  081891b38f05f9a186814ab7d1cd5c572b8f777f (commit)
       via  4472e3c7cbfb5fd33ce575bd2666036d363b2ce2 (commit)
       via  04c0fbf9fa780b4a281c5982ec33e5632852cfa1 (commit)
       via  dc19181b46dec42bc5db83861731656a5b45b899 (commit)
       via  224c7b9aeb3000e11790a2c667f0ee45c9481f17 (commit)
       via  94d43a69237b5d2bf671e384ff8b2b9a5ce445b4 (commit)
       via  c3e1e45419104bdb01dd385b22eae85bb8799611 (commit)
       via  946b527467c19236814cae6e35191ce19db3284a (commit)
       via  02e2f3a3c2ae669824d595bc9b42f37d9624b22c (commit)
       via  54210ba456b4e0822c5e333fd1f996bb35c6bee9 (commit)
       via  fd08a0dc40846f58aaa8a7df7726ac83e5e4c038 (commit)
       via  98f5e0e604e60fead409c28645b064510b8fc8de (commit)
       via  46629b17783d3ddf624002893af955d525f453a8 (commit)
       via  a1a58a7382e82256f3f6785b7bebfa4643cced67 (commit)
       via  928a439496c6040392cc03615c38aa3de45bfe87 (commit)
       via  8e28187a582820857ef2dae9b13637a3881f13ba (commit)
       via  4b3e621a4497ac99978d40b316b11581cf80d088 (commit)
       via  1ce058332d84de9bc2f37344d0b49c24d3c30551 (commit)
       via  4e0adcf35398ec9f5c3a7a4b14e9a1aedcf9f66a (commit)
       via  87d5f96fe04dab6de30c733230669703a2429701 (commit)
       via  324cf344f3e0cd9e35e50076911fe7801a7d4690 (commit)
       via  d3da4f1f4f00d13962e6f23ff1272269569be5d4 (commit)
       via  3336f575aee4072d1d88b05f7db79712384ef2d8 (commit)
       via  f6e9a1186d00e023021f5853e5a7a1d7c3f838cf (commit)
       via  f15bdf8420351828159c0d7847d4f653388bf53c (commit)
       via  a234b20dc6617392deb8a1e00eb0eed0ff353c0a (commit)
       via  97a0938cfafb96358649b9a538b00fbb3ee42ea0 (commit)
       via  023527c23812b2f999c938ff1ee5b30434209297 (commit)
       via  86632c12308c3ed099d75eb828f740c526dd7ec0 (commit)
       via  dafe7a6038499cc0432f4dd6f968b7fb1ffb119b (commit)
       via  e93cfe3f6812d8a1fef103e34d9d1a6ee88dd8d7 (commit)
       via  d1fdfd86c9b8e0120e557e2c7baaab542f9c2719 (commit)
       via  744fe91ac965c576cbe916ca39a0bef54afdcd3f (commit)
       via  ff36bea0314636d978f61923cc7292364c1808fa (commit)
       via  d686e00a5dd1efae29891e8056af324ea983d187 (commit)
       via  da57d4a08a2e14e5e6589138692674894f667f54 (commit)
       via  5aaa6c0f628ed7c2093ecdbac93a2c8cf6c94349 (commit)
       via  345b5986ff26015475b5e17506b3ca6cafaea8bd (commit)
       via  7b25152b5bbbaff6840f88ad7ff2684f8b8062a6 (commit)
       via  76a81d33b95b12341515cc6ca5718d72a242ab59 (commit)
       via  8339141a4a64043e82685f0e6793b592b7ac820a (commit)
       via  25c5c37094f9845be826f91bf5901068415891f6 (commit)
       via  9df5e254832c5c65ddf4a0895c09256e8386820f (commit)
       via  36af50328be059026859a09d402ff2d591789dfd (commit)
       via  f8c763ef2c9eb8343ead481093e2d0024e3f1cd2 (commit)
       via  f5278f2ac4becb33b93dc4c2fa7e6558a7daad8f (commit)
       via  c47f23e502e998d7885d4d3d679c1eacb1dab790 (commit)
       via  f80cdaf115a8ad80184240b283b243ac07bb9739 (commit)
       via  c2693bb84ea22e3b44617d729d8d2e8dbd99c51c (commit)
       via  2b521f9674a0d9aea7fb06d71b008e64c40922aa (commit)
       via  2f25f64f9f7a1021b67b9c37cfca41e86f5ae032 (commit)
       via  14f8767d89d62e0d573a1e6b31fbeea9272e7011 (commit)
       via  32872f1e745f45d1c0e84943cf57ad985a925f3e (commit)
       via  a82432e09cc7f3b22920d9baecba1f8e89332f37 (commit)
       via  9eddc07cd32918f3b8e9ebd114d9c8f8f39a359b (commit)
       via  143b2c6769c64eb55d2f34305ad8e2b7ce681aa6 (commit)
       via  4569c1b87f5d04f663e1c8e2813d090a78dd9e40 (commit)
       via  ee53746391bcfdaa75bba0db87add3dc7becb84d (commit)
       via  92c0c95057cd98a5b8cf8099367a122684cf329b (commit)
       via  296ec6be00c208aa9ab8cbaa20376749e8b6ab49 (commit)
       via  7deb4955ef8816885376e558e7f0eaf65e22b4d4 (commit)
       via  b916d130e0799f457634cfba2b06fc1250db54a0 (commit)
       via  1ea71dc16928433c1243375bd9210d5fceb28fe6 (commit)
       via  e7a49bee8af14cf5d4e16e14cb5bdd3ba83385b9 (commit)
       via  ed62a7f4dde0264cd60e4b739c41be9c98bbe3e4 (commit)
       via  38932d7e76c4e35aca7382640e9e86360a389545 (commit)
       via  55b3152ebb03fbe946986a52732f79a68d926163 (commit)
       via  29d36377ec206cbe52274ee9a5a6e88ef27921d5 (commit)
       via  25b33e62902b5bb181405e2177d2fe935cfe6c75 (commit)
       via  a892818fb13a1839c82104523cb6cb359c970e88 (commit)
       via  df1ab797a9d1a2a188060c70071bb8fa7d6f1a01 (commit)
       via  6fc6ecda47f8556fc0a4daabc042b0b1a2d7d592 (commit)
       via  b653f950b906ecf4194d2a2cd07fa92ae47dd546 (commit)
       via  ceef68cd1223ae14d8412adbe18af2812ade8c2d (commit)
       via  879d981650dff5ee577712aa45aa9b363b039a04 (commit)
       via  e595402348d7766d7e3f7fd8d9f32bd8b144f747 (commit)
       via  33cd7d9232078f70aa380309a3615a1a9d743988 (commit)
       via  fb26d933e7af0cc07fe686ef88ef95b28cd10d2b (commit)
       via  48d2369e3a7c0e55f5c94973ab0191ddfbaa02c1 (commit)
       via  afd75a89d7aeba622b53c5b37e3f76572ef68c3a (commit)
       via  8d9debda3a88cb20d19d23dbdfe106b3132a4ead (commit)
       via  6eba2b45ef54d671c674d7d840dc99208eb5d00c (commit)
       via  4787c281a351b04b9570b8bb2d863db706a9d9c9 (commit)
       via  9bd9fa819f30aff560da9412ffa14d87e557ab69 (commit)
       via  d53bbaf239de6059dd0d8872f84d1a0db6ccb90c (commit)
       via  7b0d9b1dc03d9343f3ccfed2fb5876d7d9f048d5 (commit)
       via  d65686925bbd04cbbfa3ca2705f54696b76beb45 (commit)
       via  4f6b51bc00d3f37b8938951217cd1f3564bedcd8 (commit)
       via  b395258c708b49a5da8d0cffcb48d83294354ba3 (commit)
       via  569b2b6e5d84277316bce84c0e118ffd116ffe5e (commit)
       via  e5687a75c06966ab68ad453093fb6c7b9d8fe761 (commit)
       via  27a4c936d87d31952f6a8377e04cae1fb90edffd (commit)
       via  7f642b767c8274a6f3344e4a5e1141e9e7f38924 (commit)
       via  7efc144e0eacc123544507d635630b7949052993 (commit)
       via  43db6eac145a6a9a59f6d892a9b9fd2384a12d51 (commit)
       via  ad6d052794693ec22f430109de624b8fd7ea212d (commit)
       via  a4f08f816c968fd9dc614f171b8925b5b7998da7 (commit)
       via  a7a3c9f8cc85f04267a5606f4413afcde7af1864 (commit)
       via  ea37ca8caa7c56fea957fc7aac066d5708675f28 (commit)
       via  e93f054a2d0cf1c21d00112ccaa93d5d2432a677 (commit)
       via  b734c55b73941125af327595ba39252add032791 (commit)
       via  daab0dcd59baee4b1e56eab640e29c9e8e947e80 (commit)
       via  00a15b4b06f9de757b30cd320fd56431980f8458 (commit)
       via  8bfccfb1fead0448e959126e07dad1a800880575 (commit)
       via  a09ced080782388e389ee6c669b9d36da65f9dc5 (commit)
       via  b21c74c08fbdd350bcd01b083fc3cf29a98e5a3a (commit)
       via  cabd3b127cf5ab2b916c5717992338316496de8b (commit)
       via  80c0a2c3eec3ac5d3ac862f9b4c5926f0eece6ed (commit)
       via  11439c030741ce402c52ad9d3cd1a407d45a443e (commit)
       via  e564debcb683454296d3e3cf478748daa0b08cbc (commit)
       via  fba4ec1d7269284b48042b1a7ea084b531bf8d9c (commit)
       via  cccc20cac1d91951bd9b707c3dfb532e9ad8686b (commit)
       via  68c9545bb7d57933e514ef55779abfd06c38145b (commit)
       via  2cf390147f147dfab2c8c63c470177189799ed20 (commit)
       via  287ee379ad62f9043699c936dc1204b6a906f52c (commit)
       via  a64f778b468ff7bd95461d1228aec66565602d57 (commit)
       via  eef87432f746406d467c647dd7302fc4cac8d53f (commit)
       via  313464eed2b54281b8b2357002cd7dfb9231ddf6 (commit)
       via  4fff42d6c9ae24effcae3f6eaa73a8a4eda5a38f (commit)
       via  df8ef3fffa7a4186d7ade60b514bb78acbb9b33c (commit)
       via  79b087eab39177799e38611661fd86125d9b8a67 (commit)
       via  efdf126e6e0fe75afa26d4d7cdcce663892a840c (commit)
       via  35a7c63a23646994af69a1a6d325ad11b4bc8354 (commit)
       via  a91fc737dd4c982194392b467b68c6c8383cec55 (commit)
       via  34c2d5bbe08e41b0802eb6ca58a8084595064803 (commit)
       via  c83d4bea0e9abc94b84315eb42c8ed7a879c8899 (commit)
       via  7e9ea719c8fda75f346eb87daae58cf06c8070e0 (commit)
       via  e4f2823d8b564c5b2c2f7029e5eb81be6b058d3c (commit)
       via  1b967a5870088c3f4c7b5a3b8202e1b2e9572690 (commit)
       via  04c515e17a08b41c9dbf241e3983148596353d2c (commit)
       via  45417c48a7d32fb6e3f7fdccc70ec2faa8441135 (commit)
       via  9c3195a542df8d7e747e28d49d7abdc707781632 (commit)
       via  96d1613a474602b833af953a364fb045fcfee776 (commit)
       via  6f8490fe9ce1d6dbef326d75f9eab79c09e83462 (commit)
       via  38cddfdc23f6d2aa31be24173f9d7c1df2e3c4bc (commit)
       via  e2e13d0bd0856496a3571ba6b7f35f41e6534859 (commit)
       via  ea1f2d6e96715f0821df5d096be46b2454e6073a (commit)
       via  04f7331a44e0bfd48f52d06f6fd463c6652dd38f (commit)
       via  5b4feea19e2cc1bdf18ce52cf0062828ad9067c7 (commit)
       via  1ce2874170c85de79a896b1cc3077d9f3032d9e0 (commit)
       via  1151e49bcec0ac69fe01d084e10f106481337dea (commit)
       via  b92cca798d702273d63d56d9197782edeb927c8c (commit)
       via  abdf624576745fa0a4f37319e3c7dbd76047e2d2 (commit)
       via  47dc61713d6bcfc16410fdb657c41dbbef85fb2c (commit)
       via  9713a50748616587ca32f17e9931c752aa1e5d3d (commit)
       via  fe141c14e201993b5e0efe848c2a47e0a4b553bc (commit)
       via  81b2d1ceaf680d740f66250b848eea1db05c4a3b (commit)
       via  583203a8ca082541bc2982752b20294bb972bdcc (commit)
       via  051d8bf74c500b8e1f1d2eabb9bb384d01104d10 (commit)
      from  75340e4d2906762ecd088180087c9229a253e4ed (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 fe21d5a407b1f779c55db60d4760ecec477c921e
Merge: 75340e4d2906762ecd088180087c9229a253e4ed a251c4f239e7b42856314412142cd9777f91dbf1
Author: zhanglikun <zhanglikun at cnnic.cn>
Date:   Wed May 18 14:42:39 2011 +0800

    aaa

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

Summary of changes:
 ChangeLog                                          |   72 +++-
 configure.ac                                       |    5 +
 doc/Doxyfile                                       |    3 +-
 src/bin/cfgmgr/b10-cfgmgr.py.in                    |   23 +-
 src/bin/cfgmgr/plugins/Makefile.am                 |    6 +-
 src/bin/cfgmgr/plugins/tests/Makefile.am           |   27 +
 src/bin/cfgmgr/plugins/tests/tsig_keys_test.py     |  103 ++++
 src/bin/cfgmgr/plugins/tsig_keys.py                |   50 ++
 src/bin/cfgmgr/plugins/tsig_keys.spec              |   21 +
 src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in         |    3 +
 src/bin/msgq/tests/msgq_test.py                    |    2 +-
 src/bin/resolver/resolver.cc                       |   16 +-
 src/bin/stats/Makefile.am                          |    7 +-
 src/bin/stats/b10-stats-httpd.8                    |    4 +
 src/bin/stats/b10-stats-httpd.xml                  |    6 +
 src/bin/stats/b10-stats.8                          |    8 +-
 src/bin/stats/b10-stats.xml                        |   16 +-
 src/bin/stats/stats-schema.spec.in                 |   87 ++++
 src/bin/stats/stats.py.in                          |   19 +-
 src/bin/stats/stats.spec.in                        |   81 +---
 src/bin/stats/stats_httpd.py.in                    |    4 +-
 src/bin/stats/tests/b10-stats_test.py              |    9 +
 src/bin/xfrin/tests/xfrin_test.py                  |   51 ++
 src/bin/xfrin/xfrin.py.in                          |   94 ++--
 src/bin/xfrin/xfrin.spec                           |    5 +
 src/bin/zonemgr/tests/zonemgr_test.py              |   96 ++--
 src/cppcheck-suppress.lst                          |    2 +-
 src/lib/Makefile.am                                |    4 +-
 src/lib/asiodns/Makefile.am                        |    6 +-
 src/lib/asiodns/asiodef.mes                        |   56 ++
 src/lib/asiodns/asiodef.msg                        |   56 --
 src/lib/asiodns/io_fetch.cc                        |   73 ++-
 src/lib/asiodns/io_fetch.h                         |   24 +
 src/lib/asiolink/tests/Makefile.am                 |    6 +
 src/lib/asiolink/tests/interval_timer_unittest.cc  |   26 +-
 src/lib/config/Makefile.am                         |   19 +-
 src/lib/config/ccsession.cc                        |   82 +++-
 src/lib/config/ccsession.h                         |   55 ++-
 src/lib/config/config_log.cc                       |   26 +
 src/lib/config/config_log.h                        |   38 ++
 src/lib/config/configdef.mes                       |   50 ++
 src/lib/config/tests/Makefile.am                   |    1 +
 src/lib/config/tests/ccsession_unittests.cc        |  122 +++++-
 src/lib/config/tests/run_unittests.cc              |    7 +
 src/lib/cryptolink/crypto_hmac.cc                  |    9 +
 src/lib/cryptolink/tests/crypto_unittests.cc       |   74 ++-
 src/lib/datasrc/Makefile.am                        |   15 +-
 src/lib/datasrc/cache.cc                           |   23 +-
 src/lib/datasrc/data_source.cc                     |   76 +++-
 src/lib/datasrc/logger.cc                          |   23 +
 src/lib/datasrc/logger.h                           |   46 ++
 src/lib/datasrc/memory_datasrc.cc                  |   60 +++
 src/lib/datasrc/messagedef.mes                     |  498 ++++++++++++++++++
 src/lib/datasrc/sqlite3_datasrc.cc                 |   37 ++-
 src/lib/datasrc/static_datasrc.cc                  |    5 +
 src/lib/datasrc/tests/Makefile.am                  |    6 +-
 src/lib/datasrc/tests/logger_unittest.cc           |   31 ++
 src/lib/dns/message.h                              |    1 +
 src/lib/dns/python/Makefile.am                     |    3 +
 src/lib/dns/python/message_python.cc               |    8 +-
 src/lib/dns/python/pydnspp.cc                      |   31 +-
 src/lib/dns/python/pydnspp_common.cc               |   18 +-
 src/lib/dns/python/pydnspp_common.h                |   29 +-
 src/lib/dns/python/rcode_python.cc                 |  157 +++---
 src/lib/dns/python/rcode_python.h                  |   57 ++
 src/lib/dns/python/tests/Makefile.am               |    1 +
 src/lib/dns/python/tests/tsigerror_python_test.py  |   97 ++++
 src/lib/dns/python/tests/tsigkey_python_test.py    |   31 +-
 src/lib/dns/python/tsig_python.cc                  |    1 -
 src/lib/dns/python/tsigerror_python.cc             |  362 +++++++++++++
 src/lib/dns/python/tsigerror_python.h              |   44 ++
 src/lib/dns/python/tsigerror_python_inc.cc         |   83 +++
 src/lib/dns/python/tsigkey_python.cc               |    6 +-
 src/lib/dns/tests/testdata/Makefile.am             |    8 +
 src/lib/dns/tests/testdata/gen-wiredata.py.in      |   13 +-
 src/lib/dns/tests/testdata/tsig_verify1.spec       |   19 +
 src/lib/dns/tests/testdata/tsig_verify10.spec      |   22 +
 src/lib/dns/tests/testdata/tsig_verify2.spec       |   32 ++
 src/lib/dns/tests/testdata/tsig_verify3.spec       |   26 +
 src/lib/dns/tests/testdata/tsig_verify4.spec       |   27 +
 src/lib/dns/tests/testdata/tsig_verify5.spec       |   26 +
 src/lib/dns/tests/testdata/tsig_verify6.spec       |   21 +
 src/lib/dns/tests/testdata/tsig_verify7.spec       |   21 +
 src/lib/dns/tests/testdata/tsig_verify8.spec       |   23 +
 src/lib/dns/tests/testdata/tsig_verify9.spec       |   21 +
 src/lib/dns/tests/tsig_unittest.cc                 |  534 +++++++++++++++++---
 src/lib/dns/tests/tsigerror_unittest.cc            |   14 +
 src/lib/dns/tests/tsigkey_unittest.cc              |   88 +++-
 src/lib/dns/tests/tsigrecord_unittest.cc           |    9 +-
 src/lib/dns/tsig.cc                                |  380 ++++++++++++---
 src/lib/dns/tsig.h                                 |  198 +++++++-
 src/lib/dns/tsigerror.cc                           |   11 +
 src/lib/dns/tsigerror.h                            |   24 +-
 src/lib/dns/tsigkey.cc                             |   26 +-
 src/lib/dns/tsigkey.h                              |   42 ++-
 src/lib/dns/tsigrecord.cc                          |    6 +-
 src/lib/dns/tsigrecord.h                           |   43 ++-
 src/lib/log/Makefile.am                            |    2 +-
 src/lib/log/README                                 |  115 +++--
 src/lib/log/compiler/message.cc                    |   36 +-
 src/lib/log/log_formatter.h                        |    6 +-
 src/lib/log/logger_support.cc                      |   79 +++-
 src/lib/log/logger_support.h                       |   31 ++
 src/lib/log/message_exception.cc                   |   26 -
 src/lib/log/message_exception.h                    |   37 +-
 src/lib/log/message_reader.cc                      |  182 ++++---
 src/lib/log/message_reader.h                       |   19 +-
 src/lib/log/messagedef.cc                          |   70 ++--
 src/lib/log/messagedef.h                           |   14 +-
 src/lib/log/messagedef.mes                         |  202 ++++----
 src/lib/log/tests/logger_support_test.cc           |   17 +-
 src/lib/log/tests/message_dictionary_unittest.cc   |    4 +-
 src/lib/log/tests/message_reader_unittest.cc       |   47 +-
 src/lib/log/tests/run_time_init_test.sh.in         |   41 +-
 src/lib/nsas/Makefile.am                           |   31 +-
 src/lib/nsas/nameserver_address_store.cc           |    7 +
 src/lib/nsas/nameserver_entry.cc                   |   24 +-
 src/lib/nsas/nsas_log.cc                           |   26 +
 src/lib/nsas/nsas_log.h                            |   53 ++
 src/lib/nsas/nsasdef.mes                           |   61 +++
 src/lib/nsas/tests/Makefile.am                     |    1 +
 src/lib/nsas/tests/run_unittests.cc                |    9 +
 src/lib/python/bind10_config.py.in                 |   38 ++-
 src/lib/python/isc/notify/notify_out.py            |    8 +-
 src/lib/python/isc/notify/tests/notify_out_test.py |   50 ++-
 src/lib/python/isc/util/Makefile.am                |    2 +-
 src/lib/python/isc/util/file.py                    |   29 +
 src/lib/python/isc/util/tests/Makefile.am          |    2 +-
 src/lib/python/isc/util/tests/file_test.py         |   32 ++
 src/lib/resolve/recursive_query.cc                 |  252 ++++++++--
 src/lib/resolve/recursive_query.h                  |   14 +
 src/lib/resolve/tests/recursive_query_unittest.cc  |  134 +++---
 src/lib/server_common/Makefile.am                  |    3 +
 src/lib/server_common/keyring.cc                   |   61 +++
 src/lib/server_common/keyring.h                    |   96 ++++
 src/lib/server_common/tests/Makefile.am            |    4 +
 src/lib/server_common/tests/data_path.h.in         |   16 +
 src/lib/server_common/tests/keyring_test.cc        |  132 +++++
 src/lib/server_common/tests/testdata/spec.spec     |    6 +
 src/lib/util/Makefile.am                           |    2 +
 src/lib/util/python/mkpywrapper.py.in              |  100 ++++
 src/lib/util/python/pycppwrapper_util.h            |  308 +++++++++++
 src/lib/util/python/wrapper_template.cc            |  293 +++++++++++
 src/lib/util/python/wrapper_template.h             |   44 ++
 144 files changed, 6487 insertions(+), 1216 deletions(-)
 create mode 100644 src/bin/cfgmgr/plugins/tests/Makefile.am
 create mode 100644 src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
 create mode 100644 src/bin/cfgmgr/plugins/tsig_keys.py
 create mode 100644 src/bin/cfgmgr/plugins/tsig_keys.spec
 create mode 100644 src/bin/stats/stats-schema.spec.in
 create mode 100644 src/lib/asiodns/asiodef.mes
 delete mode 100644 src/lib/asiodns/asiodef.msg
 create mode 100644 src/lib/config/config_log.cc
 create mode 100644 src/lib/config/config_log.h
 create mode 100644 src/lib/config/configdef.mes
 create mode 100644 src/lib/datasrc/logger.cc
 create mode 100644 src/lib/datasrc/logger.h
 create mode 100644 src/lib/datasrc/messagedef.mes
 create mode 100644 src/lib/datasrc/tests/logger_unittest.cc
 create mode 100644 src/lib/dns/python/rcode_python.h
 create mode 100644 src/lib/dns/python/tests/tsigerror_python_test.py
 create mode 100644 src/lib/dns/python/tsigerror_python.cc
 create mode 100644 src/lib/dns/python/tsigerror_python.h
 create mode 100644 src/lib/dns/python/tsigerror_python_inc.cc
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify1.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify10.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify2.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify3.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify4.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify5.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify6.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify7.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify8.spec
 create mode 100644 src/lib/dns/tests/testdata/tsig_verify9.spec
 delete mode 100644 src/lib/log/message_exception.cc
 create mode 100644 src/lib/nsas/nsas_log.cc
 create mode 100644 src/lib/nsas/nsas_log.h
 create mode 100644 src/lib/nsas/nsasdef.mes
 create mode 100644 src/lib/python/isc/util/file.py
 create mode 100644 src/lib/python/isc/util/tests/file_test.py
 create mode 100644 src/lib/server_common/keyring.cc
 create mode 100644 src/lib/server_common/keyring.h
 create mode 100644 src/lib/server_common/tests/data_path.h.in
 create mode 100644 src/lib/server_common/tests/keyring_test.cc
 create mode 100644 src/lib/server_common/tests/testdata/spec.spec
 create mode 100755 src/lib/util/python/mkpywrapper.py.in
 create mode 100644 src/lib/util/python/pycppwrapper_util.h
 create mode 100644 src/lib/util/python/wrapper_template.cc
 create mode 100644 src/lib/util/python/wrapper_template.h

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 6493380..7fd734b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,13 +1,77 @@
+<<<<<<< HEAD
 232.	[func]*		feng
 	Add optional hmac algorithm SHA224/384/812.
 	(Trac#782).
 	
 231.	[func]*
+=======
+239.	[bug]		jerry
+	src/bin/xfrout: If a zone doesn't have notify slaves(only has
+	one apex ns record - the primary master name server) will cause
+	b10-xfrout uses 100% of CPU.
+	(Trac #684, git d11b5e89203a5340d4e5ca51c4c02db17c33dc1f)
+
+238.	[func]		zhang likun
+	Implement the simplest forwarder, which pass everything throught
+	except QID, port number. The response will not be cached.
+	(Trac #598_new, git 8e28187a582820857ef2dae9b13637a3881f13ba)
+
+237.	[bug]		naokikambe
+	Resolved that the stats module wasn't configurable in bindctl in
+	spite of its having configuration items. The configuration part
+	was removed from the original spec file "stats.spec" and was
+	placed in a new spec file "stats-schema.spec". Because it means
+	definitions of statistics items. The command part is still
+	there. Thus stats module currently has no its own configuration,
+	and the items in "stats-schema.spec" are neither visible nor
+	configurable through bindctl. "stats-schema.spec" is shared with
+	stats module and stats-httpd module, and maybe with other
+	statistical modules in future. "stats.spec" has own configuration
+	and commands of stats module, if it requires.
+	(Trac#719, git a234b20dc6617392deb8a1e00eb0eed0ff353c0a)
+
+236.	[func]		jelte
+	C++ client side of configuration now uses BIND10 logging system.
+	It also has improved error handling when communicating with the
+	rest of the system.
+	(Trac #743, git 86632c12308c3ed099d75eb828f740c526dd7ec0)
+
+235.	[func]		jinmei
+	libdns++: added support for TSIG signing and verification.  It can
+	be done using a newly introduced TSIGContext class.
+	Note: we temporarily disabled support for truncated signature
+	and modified some part of the code introduced in #226 accordingly.
+	We plan to fix this pretty soon.
+	(Trac #812, git ebe0c4b1e66d359227bdd1bd47395fee7b957f14)
+	(Trac #871, git 7c54055c0e47c7a0e36fcfab4b47ff180c0ca8c8)
+	(Trac #813, git ffa2f0672084c1f16e5784cdcdd55822f119feaa)
+	(Trac #893, git 5aaa6c0f628ed7c2093ecdbac93a2c8cf6c94349)
+
+234.	[func]		jerry
+	src/bin/xfrin: update xfrin to use TSIG. Currently it only supports
+	sending a signed TSIG request or SOA request.
+	(Trac #815, git a892818fb13a1839c82104523cb6cb359c970e88)
+
+233.	[func]		stephen
+	Added new-style logging statements to the NSAS code.
+	(Trac #745, git ceef68cd1223ae14d8412adbe18af2812ade8c2d)
+
+232.	[func]		stephen
+	To facilitate the writing of extended descriptions in
+	message files, altered the message file format.  The message
+	is now flagged with a "%" as the first non-blank character
+	in the line and the lines in the extended description are
+	no longer preceded by a "+".
+	(Trac #900, git b395258c708b49a5da8d0cffcb48d83294354ba3)
+
+231.	[func]*		vorner
+>>>>>>> master
 	The logging interface changed slightly. We use
-	logger.foo(MESSAGE_ID).arg(bar); instead of logger.foo(MESSAGE_ID, bar);
-	internally. The message definitions use '%1,%2,...' instead of '%s,%d',
-	which allows us to cope better with mismatched placeholders and allows
-	reordering of them in case of translation.
+	logger.foo(MESSAGE_ID).arg(bar); instead of logger.foo(MESSAGE_ID,
+	bar); internally. The message definitions use '%1,%2,...'
+	instead of '%s,%d', which allows us to cope better with
+	mismatched placeholders and allows reordering of them in
+	case of translation.
 	(Trac901, git 4903410e45670b30d7283f5d69dc28c2069237d6)
 
 230.	[bug]		naokikambe
diff --git a/configure.ac b/configure.ac
index d76500d..d478924 100644
--- a/configure.ac
+++ b/configure.ac
@@ -686,6 +686,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/bindctl/tests/Makefile
                  src/bin/cfgmgr/Makefile
                  src/bin/cfgmgr/plugins/Makefile
+                 src/bin/cfgmgr/plugins/tests/Makefile
                  src/bin/cfgmgr/tests/Makefile
                  src/bin/host/Makefile
                  src/bin/loadzone/Makefile
@@ -807,6 +808,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/stats/stats.py
            src/bin/stats/stats_httpd.py
            src/bin/stats/stats.spec
+           src/bin/stats/stats-schema.spec
            src/bin/stats/stats-httpd.spec
            src/bin/stats/stats-httpd-xml.tpl
            src/bin/stats/stats-httpd-xsd.tpl
@@ -840,6 +842,8 @@ AC_OUTPUT([doc/version.ent
            src/lib/cc/session_config.h.pre
            src/lib/cc/tests/session_unittests_config.h
            src/lib/log/tests/run_time_init_test.sh
+           src/lib/util/python/mkpywrapper.py
+           src/lib/server_common/tests/data_path.h
            tests/system/conf.sh
            tests/system/glue/setup.sh
            tests/system/glue/nsx1/b10-config.db
@@ -865,6 +869,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
            chmod +x src/lib/log/tests/run_time_init_test.sh
+           chmod +x src/lib/util/python/mkpywrapper.py
            chmod +x tests/system/conf.sh
           ])
 AC_OUTPUT
diff --git a/doc/Doxyfile b/doc/Doxyfile
index a57d275..05b6ef3 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -574,6 +574,7 @@ INPUT                  = ../src/lib/cc ../src/lib/config \
     ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/
+    ../src/lib/resolve
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -1164,7 +1165,7 @@ XML_DTD                =
 # and cross-referencing information) to the XML output. Note that
 # enabling this will significantly increase the size of the XML output.
 
-XML_PROGRAMLISTING     = YES
+XML_PROGRAMLISTING     = NO
 
 #---------------------------------------------------------------------------
 # configuration options for the AutoGen Definitions output
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index 16c8f76..d91dfca 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -18,6 +18,7 @@
 import sys; sys.path.append ('@@PYTHONPATH@@')
 
 from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError
+import bind10_config
 from isc.cc import SessionError
 import isc.util.process
 import signal
@@ -28,24 +29,10 @@ import os.path
 
 isc.util.process.rename()
 
-# If B10_FROM_SOURCE is set in the environment, we use data files
-# from a directory relative to the value of that variable, or, if defined,
-# relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR.  Otherwise
-# we use the ones installed on the system.
-# B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
-# tests where we want to use variuos types of configuration within the test
-# environment.  (We may want to make it even more generic so that the path is
-# passed from the boss process)
-if "B10_FROM_SOURCE" in os.environ:
-    if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
-        DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
-    else:
-        DATA_PATH = os.environ["B10_FROM_SOURCE"]
-    PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
-else:
-    PREFIX = "@prefix@"
-    DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
-    PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+# Import some paths from our configuration
+DATA_PATH = bind10_config.DATA_PATH
+PLUGIN_PATHS = bind10_config.PLUGIN_PATHS
+PREFIX = bind10_config.PREFIX
 DEFAULT_CONFIG_FILE = "b10-config.db"
 
 cm = None
diff --git a/src/bin/cfgmgr/plugins/Makefile.am b/src/bin/cfgmgr/plugins/Makefile.am
index 952fde6..d83c2bb 100644
--- a/src/bin/cfgmgr/plugins/Makefile.am
+++ b/src/bin/cfgmgr/plugins/Makefile.am
@@ -1 +1,5 @@
-EXTRA_DIST = README
+SUBDIRS = tests
+EXTRA_DIST = README tsig_keys.py tsig_keys.spec
+
+config_plugindir = @prefix@/share/@PACKAGE@/config_plugins
+config_plugin_DATA = tsig_keys.py tsig_keys.spec
diff --git a/src/bin/cfgmgr/plugins/tests/Makefile.am b/src/bin/cfgmgr/plugins/tests/Makefile.am
new file mode 100644
index 0000000..896dab7
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tests/Makefile.am
@@ -0,0 +1,27 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = tsig_keys_test.py
+
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	env B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
+	$(LIBRARY_PATH_PLACEHOLDER) \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+	done
+
diff --git a/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
new file mode 100644
index 0000000..be2921c
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2011  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.
+
+# Make sure we can load the module, put it into path
+import sys
+import os
+sys.path.extend(os.environ["B10_TEST_PLUGIN_DIR"].split(':'))
+
+import tsig_keys
+import unittest
+import isc.config.module_spec
+
+class TSigKeysTest(unittest.TestCase):
+    def test_load(self):
+        """
+        Checks the entry point returns the correct values.
+        """
+        (spec, check) = tsig_keys.load()
+        # It returns the checking function
+        self.assertEqual(check, tsig_keys.check)
+        # The plugin stores it's spec
+        self.assertEqual(spec, tsig_keys.spec)
+
+    def test_spec(self):
+        """
+        Checks the spec is looking sane (doesn't do really deep check here).
+        """
+        spec = tsig_keys.spec
+        # In python, we don't generally check the type of something, because
+        # of the duck typing.
+        # But this is unittest, so we check it does what we intend and
+        # supplying that's behaving the same but is different is not our
+        # intention
+        self.assertTrue(isinstance(spec, isc.config.module_spec.ModuleSpec))
+        # Correct name
+        self.assertEqual("tsig_keys", spec.get_module_name())
+        # There are no commands, nobody would handle them anyway
+        self.assertEqual([], spec.get_commands_spec())
+        # There's some nonempty configuration
+        self.assertNotEqual({}, spec.get_config_spec())
+
+    def test_missing_keys(self):
+        """
+        Test that missing keys doesn't kill us. There are just no keys there.
+        """
+        self.assertEqual(None, tsig_keys.check({}))
+
+    def test_data_empty(self):
+        """Check we accept valid config with empty set of tsig keys."""
+        self.assertEqual(None, tsig_keys.check({'keys': []}))
+
+    def test_keys_valid(self):
+        """
+        Check we accept some valid keys (we don't check all the algorithms,
+        that's the job of isc.dns.TSIGKey).
+        """
+        self.assertEqual(None, tsig_keys.check({'keys':
+            ['testkey:QklORCAxMCBpcyBjb29sCg==',
+             'test.key:QklORCAxMCBpcyBjb29sCg==:hmac-sha1']}))
+
+    def test_keys_same_name(self):
+        """
+        Test we reject when we have multiple keys with the same name.
+        """
+        self.assertEqual("Multiple TSIG keys with name 'test.key.'",
+                         tsig_keys.check({'keys':
+                                         ['test.key:QklORCAxMCBpcyBjb29sCg==',
+                                          'test.key:b3RoZXIK']}))
+
+    def test_invalid_key(self):
+        """
+        Test we reject invalid key.
+        """
+        self.assertEqual("TSIG: Invalid TSIG key string: invalid.key",
+                         tsig_keys.check({'keys': ['invalid.key']}))
+        self.assertEqual(
+            "TSIG: attempt to decode a value not in base64 char set",
+            tsig_keys.check({'keys': ['invalid.key:123']}))
+
+    def test_bad_format(self):
+        """
+        Test we fail on bad format. We don't really care much how here, though,
+        as this should not get in trough config manager anyway.
+        """
+        self.assertNotEqual(None, tsig_keys.check({'bad_name': {}}))
+        self.assertNotEqual(None, tsig_keys.check({'keys': 'not_list'}))
+        self.assertNotEqual(None, tsig_keys.check({'keys': 42}))
+        self.assertNotEqual(None, tsig_keys.check({'keys': {}}))
+
+if __name__ == '__main__':
+        unittest.main()
diff --git a/src/bin/cfgmgr/plugins/tsig_keys.py b/src/bin/cfgmgr/plugins/tsig_keys.py
new file mode 100644
index 0000000..d57e645
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tsig_keys.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2011  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.
+
+# This is the plugin for tsig_keys configuration section. The TSIG keyring
+# lives there (eg. all the shared secrets, with some exceptions where there
+# are some TSIG keys elsewhere, but these should be removed soon). We do
+# sanity checking of user configuration here, simply by trying to construct
+# all the keys here.
+
+from isc.config.module_spec import module_spec_from_file
+from isc.util.file import path_search
+from pydnspp import TSIGKey, InvalidParameter
+from bind10_config import PLUGIN_PATHS
+spec = module_spec_from_file(path_search('tsig_keys.spec', PLUGIN_PATHS))
+
+def check(config):
+    # Check the data layout first
+    errors=[]
+    if not spec.validate_config(False, config, errors):
+        return ' '.join(errors)
+    # Get the list of keys, if any
+    keys = config.get('keys', [])
+    # Run through them, check they can be constructed and there are no
+    # duplicates
+    keyNames = set()
+    for key in keys:
+        try:
+            name = str(TSIGKey(key).get_key_name())
+        except InvalidParameter as e:
+            return "TSIG: " + str(e)
+        if name in keyNames:
+            return "Multiple TSIG keys with name '" + name + "'"
+        keyNames.add(name)
+    # No error found, so let's assume it's OK
+    return None
+
+def load():
+    return (spec, check)
diff --git a/src/bin/cfgmgr/plugins/tsig_keys.spec b/src/bin/cfgmgr/plugins/tsig_keys.spec
new file mode 100644
index 0000000..e558dd2
--- /dev/null
+++ b/src/bin/cfgmgr/plugins/tsig_keys.spec
@@ -0,0 +1,21 @@
+{
+    "module_spec": {
+        "module_name": "tsig_keys",
+        "module_description": "The TSIG keyring is stored here",
+        "config_data": [
+            {
+                "item_name": "keys",
+                "item_type": "list",
+                "item_optional": false,
+                "item_default": [],
+                "list_item_spec": {
+                    "item_name": "key",
+                    "item_type": "string",
+                    "item_optional": false,
+                    "item_default": ""
+                }
+            }
+        ],
+        "commands": []
+    }
+}
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index 37cd0f5..ea5fc8b 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -20,6 +20,7 @@
 import unittest
 import os
 import sys
+import bind10_config
 from isc.testutils.parse_args import OptsError, TestOptParser
 
 class MyConfigManager:
@@ -110,6 +111,7 @@ class TestConfigManagerStartup(unittest.TestCase):
             env_var = os.environ["B10_FROM_SOURCE"]
 
         os.environ["B10_FROM_SOURCE"] = tmp_env_var
+        bind10_config.reload()
         b = __import__("b10-cfgmgr", globals(), locals())
         b.PLUGIN_PATH = [] # It's enough to test plugins in one test
         b.ConfigManager = MyConfigManager
@@ -117,6 +119,7 @@ class TestConfigManagerStartup(unittest.TestCase):
 
         if env_var != None:
             os.environ["B10_FROM_SOURCE"] = env_var
+        bind10_config.reload()
 
         sys.modules.pop("b10-cfgmgr")
 
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 26878f7..fe4f7d4 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -202,7 +202,7 @@ class SendNonblock(unittest.TestCase):
                 try:
                     def killall(signum, frame):
                         os.kill(queue_pid, signal.SIGTERM)
-                        sys.exit(1)
+                        os._exit(1)
                     signal.signal(signal.SIGALRM, killall)
                     msg = msgq.preparemsg({"type" : "ping"}, data)
                     now = time.clock()
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 591e214..e43b48e 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -144,7 +144,7 @@ public:
     void resolve(const isc::dns::QuestionPtr& question,
         const isc::resolve::ResolverInterface::CallbackPtr& callback);
 
-    void processNormalQuery(const Question& question,
+    void processNormalQuery(ConstMessagePtr query_message,
                             MessagePtr answer_message,
                             OutputBufferPtr buffer,
                             DNSServer* server);
@@ -468,7 +468,7 @@ Resolver::processMessage(const IOMessage& io_message,
             // The RecursiveQuery object will post the "resume" event to the
             // DNSServer when an answer arrives, so we don't have to do it now.
             sendAnswer = false;
-            impl_->processNormalQuery(*question, answer_message,
+            impl_->processNormalQuery(query_message, answer_message,
                                       buffer, server);
         }
     }
@@ -486,13 +486,19 @@ ResolverImpl::resolve(const QuestionPtr& question,
 }
 
 void
-ResolverImpl::processNormalQuery(const Question& question,
+ResolverImpl::processNormalQuery(ConstMessagePtr query_message,
                                  MessagePtr answer_message,
                                  OutputBufferPtr buffer,
                                  DNSServer* server)
 {
-    dlog("Processing normal query");
-    rec_query_->resolve(question, answer_message, buffer, server);
+    if (upstream_.empty()) {
+        dlog("Processing normal query");
+        ConstQuestionPtr question = *query_message->beginQuestion();
+        rec_query_->resolve(*question, answer_message, buffer, server);
+    } else {
+        dlog("Processing forward query");
+        rec_query_->forward(query_message, answer_message, buffer, server);
+    }
 }
 
 ConstElementPtr
diff --git a/src/bin/stats/Makefile.am b/src/bin/stats/Makefile.am
index 485bc05..e4a4f92 100644
--- a/src/bin/stats/Makefile.am
+++ b/src/bin/stats/Makefile.am
@@ -5,7 +5,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 pkglibexec_SCRIPTS = b10-stats b10-stats-httpd
 
 b10_statsdir = $(pkgdatadir)
-b10_stats_DATA = stats.spec stats-httpd.spec
+b10_stats_DATA = stats.spec stats-httpd.spec stats-schema.spec
 b10_stats_DATA += stats-httpd-xml.tpl stats-httpd-xsd.tpl stats-httpd-xsl.tpl
 
 CLEANFILES = b10-stats stats.pyc
@@ -13,7 +13,7 @@ CLEANFILES += b10-stats-httpd stats_httpd.pyc
 
 man_MANS = b10-stats.8 b10-stats-httpd.8
 EXTRA_DIST = $(man_MANS) b10-stats.xml b10-stats-httpd.xml
-EXTRA_DIST += stats.spec stats-httpd.spec
+EXTRA_DIST += stats.spec stats-httpd.spec stats-schema.spec
 EXTRA_DIST += stats-httpd-xml.tpl stats-httpd-xsd.tpl stats-httpd-xsl.tpl
 
 if ENABLE_MAN
@@ -28,8 +28,7 @@ endif
 
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
 b10-stats: stats.py
-	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
-	       -e "s|.*#@@REMOVED@@$$||"  stats.py >$@
+	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|"  stats.py >$@
 	chmod a+x $@
 
 b10-stats-httpd: stats_httpd.py
diff --git a/src/bin/stats/b10-stats-httpd.8 b/src/bin/stats/b10-stats-httpd.8
index c066f91..ed4aafa 100644
--- a/src/bin/stats/b10-stats-httpd.8
+++ b/src/bin/stats/b10-stats-httpd.8
@@ -66,6 +66,10 @@ bindctl(1)\&. Please see the manual of
 bindctl(1)
 about how to configure the settings\&.
 .PP
+/usr/local/share/bind10\-devel/stats\-schema\&.spec
+\(em This is a spec file for data schema of of BIND 10 statistics\&. This schema cannot be configured via
+bindctl(1)\&.
+.PP
 
 /usr/local/share/bind10\-devel/stats\-httpd\-xml\&.tpl
 \(em the template file of XML document\&.
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
index 5cf3b4b..34c704f 100644
--- a/src/bin/stats/b10-stats-httpd.xml
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -112,6 +112,12 @@
       of <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum> about
       how to configure the settings.
     </para>
+    <para><filename>/usr/local/share/bind10-devel/stats-schema.spec</filename>
+      <!--TODO: The filename should be computed from prefix-->
+      — This is a spec file for data schema of
+      of BIND 10 statistics. This schema cannot be configured 
+      via <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
+    </para>
     <para>
       <filename>/usr/local/share/bind10-devel/stats-httpd-xml.tpl</filename>
       <!--TODO: The filename should be computed from prefix-->
diff --git a/src/bin/stats/b10-stats.8 b/src/bin/stats/b10-stats.8
index 5714234..f69e4d3 100644
--- a/src/bin/stats/b10-stats.8
+++ b/src/bin/stats/b10-stats.8
@@ -63,11 +63,17 @@ switches to verbose mode\&. It sends verbose messages to STDOUT\&.
 .PP
 /usr/local/share/bind10\-devel/stats\&.spec
 \(em This is a spec file for
-\fBb10\-stats\fR\&. It contains definitions of statistics items of BIND 10 and commands received via
+\fBb10\-stats\fR\&. It contains commands for
+\fBb10\-stats\fR\&. They can be invoked via
+bindctl(1)\&.
+.PP
+/usr/local/share/bind10\-devel/stats\-schema\&.spec
+\(em This is a spec file for data schema of of BIND 10 statistics\&. This schema cannot be configured via
 bindctl(1)\&.
 .SH "SEE ALSO"
 .PP
 
+\fBb10-stats-httpd\fR(8),
 \fBbind10\fR(8),
 \fBbindctl\fR(1),
 \fBb10-auth\fR(8),
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 7ec58dd..f0c472d 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -89,10 +89,17 @@
   <refsect1>
     <title>FILES</title>
     <para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
+      <!--TODO: The filename should be computed from prefix-->
       — This is a spec file for <command>b10-stats</command>. It
-      contains definitions of statistics items of BIND 10 and commands
-      received via
-      <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
+      contains commands for <command>b10-stats</command>. They can be
+      invoked
+      via <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
+    </para>
+    <para><filename>/usr/local/share/bind10-devel/stats-schema.spec</filename>
+      <!--TODO: The filename should be computed from prefix-->
+      — This is a spec file for data schema of
+      of BIND 10 statistics. This schema cannot be configured 
+      via <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>.
     </para>
   </refsect1>
 
@@ -100,6 +107,9 @@
     <title>SEE ALSO</title>
     <para>
       <citerefentry>
+        <refentrytitle>b10-stats-httpd</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
diff --git a/src/bin/stats/stats-schema.spec.in b/src/bin/stats/stats-schema.spec.in
new file mode 100644
index 0000000..37e9c1a
--- /dev/null
+++ b/src/bin/stats/stats-schema.spec.in
@@ -0,0 +1,87 @@
+{
+  "module_spec": {
+    "module_name": "Stats",
+    "module_description": "Statistics data schema",
+    "config_data": [
+      {
+        "item_name": "report_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Report time",
+        "item_description": "A date time when stats module reports",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "bind10.boot_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "bind10.BootTime",
+        "item_description": "A date time when bind10 process starts initially",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.boot_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "stats.BootTime",
+        "item_description": "A date time when the stats module starts initially or when the stats module restarts",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.start_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "stats.StartTime",
+        "item_description": "A date time when the stats module starts collecting data or resetting values last time",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.last_update_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "stats.LastUpdateTime",
+        "item_description": "The latest date time when the stats module receives from other modules like auth server or boss process and so on",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "stats.timestamp",
+        "item_type": "real",
+        "item_optional": false,
+        "item_default": 0.0,
+        "item_title": "stats.Timestamp",
+        "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)",
+        "item_format": "second"
+      },
+      {
+        "item_name": "stats.lname",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "",
+        "item_title": "stats.LocalName",
+        "item_description": "A localname of stats module given via CC protocol"
+      },
+      {
+        "item_name": "auth.queries.tcp",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "auth.queries.tcp",
+        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
+      },
+      {
+        "item_name": "auth.queries.udp",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "auth.queries.udp",
+        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
+      }
+    ],
+    "commands": []
+  }
+}
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 821a719..969676e 100644
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -24,12 +24,6 @@ from optparse import OptionParser, OptionValueError
 from collections import defaultdict
 from isc.config.ccsession import ModuleCCSession, create_answer
 from isc.cc import Session, SessionError
-# Note: Following lines are removed in b10-stats	#@@REMOVED@@
-if __name__ == 'stats':					#@@REMOVED@@
-    try:						#@@REMOVED@@
-        from fake_time import time, strftime, gmtime	#@@REMOVED@@
-    except ImportError:					#@@REMOVED@@
-        pass						#@@REMOVED@@
 
 # for setproctitle
 import isc.util.process
@@ -39,13 +33,15 @@ isc.util.process.rename()
 # from a directory relative to that, otherwise we use the ones
 # installed on the system
 if "B10_FROM_SOURCE" in os.environ:
-    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
-        "src" + os.sep + "bin" + os.sep + "stats" + os.sep + "stats.spec"
+    BASE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
+        "src" + os.sep + "bin" + os.sep + "stats"
 else:
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"
-    SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "stats.spec"
-    SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+    BASE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@"
+    BASE_LOCATION = BASE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats.spec"
+SCHEMA_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-schema.spec"
 
 class Singleton(type):
     """
@@ -184,8 +180,7 @@ class CCSessionListener(Listener):
         self.session = self.subject.session = self.cc_session._session
 
         # initialize internal data
-        self.config_spec = self.cc_session.get_module_spec().get_config_spec()
-        self.stats_spec = self.config_spec
+        self.stats_spec = isc.config.module_spec_from_file(SCHEMA_SPECFILE_LOCATION).get_config_spec()
         self.stats_data = self.initialize_data(self.stats_spec)
 
         # add event handler invoked via SessionSubject object
diff --git a/src/bin/stats/stats.spec.in b/src/bin/stats/stats.spec.in
index 4d42ebf..25f6b54 100644
--- a/src/bin/stats/stats.spec.in
+++ b/src/bin/stats/stats.spec.in
@@ -2,86 +2,7 @@
   "module_spec": {
     "module_name": "Stats",
     "module_description": "Stats daemon",
-    "config_data": [
-      {
-        "item_name": "report_time",
-        "item_type": "string",
-        "item_optional": false,
-        "item_default": "1970-01-01T00:00:00Z",
-        "item_title": "Report time",
-        "item_description": "A date time when stats module reports",
-        "item_format": "date-time"
-      },
-      {
-        "item_name": "bind10.boot_time",
-        "item_type": "string",
-        "item_optional": false,
-        "item_default": "1970-01-01T00:00:00Z",
-        "item_title": "bind10.BootTime",
-        "item_description": "A date time when bind10 process starts initially",
-        "item_format": "date-time"
-      },
-      {
-        "item_name": "stats.boot_time",
-        "item_type": "string",
-        "item_optional": false,
-        "item_default": "1970-01-01T00:00:00Z",
-        "item_title": "stats.BootTime",
-        "item_description": "A date time when the stats module starts initially or when the stats module restarts",
-        "item_format": "date-time"
-      },
-      {
-        "item_name": "stats.start_time",
-        "item_type": "string",
-        "item_optional": false,
-        "item_default": "1970-01-01T00:00:00Z",
-        "item_title": "stats.StartTime",
-        "item_description": "A date time when the stats module starts collecting data or resetting values last time",
-        "item_format": "date-time"
-      },
-      {
-        "item_name": "stats.last_update_time",
-        "item_type": "string",
-        "item_optional": false,
-        "item_default": "1970-01-01T00:00:00Z",
-        "item_title": "stats.LastUpdateTime",
-        "item_description": "The latest date time when the stats module receives from other modules like auth server or boss process and so on",
-        "item_format": "date-time"
-      },
-      {
-        "item_name": "stats.timestamp",
-        "item_type": "real",
-        "item_optional": false,
-        "item_default": 0.0,
-        "item_title": "stats.Timestamp",
-        "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)",
-        "item_format": "second"
-      },
-      {
-        "item_name": "stats.lname",
-        "item_type": "string",
-        "item_optional": false,
-        "item_default": "",
-        "item_title": "stats.LocalName",
-        "item_description": "A localname of stats module given via CC protocol"
-      },
-      {
-        "item_name": "auth.queries.tcp",
-        "item_type": "integer",
-        "item_optional": false,
-        "item_default": 0,
-        "item_title": "auth.queries.tcp",
-        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
-      },
-      {
-        "item_name": "auth.queries.udp",
-        "item_type": "integer",
-        "item_optional": false,
-        "item_default": 0,
-        "item_title": "auth.queries.udp",
-        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
-      }
-    ],
+    "config_data": [],
     "commands": [
       {
         "command_name": "status",
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index dd9220e..97e9c78 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -46,7 +46,7 @@ else:
     BASE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@"
     BASE_LOCATION = BASE_LOCATION.replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
 SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd.spec"
-STATS_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats.spec"
+SCHEMA_SPECFILE_LOCATION = BASE_LOCATION + os.sep + "stats-schema.spec"
 XML_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xml.tpl"
 XSD_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsd.tpl"
 XSL_TEMPLATE_LOCATION = BASE_LOCATION + os.sep + "stats-httpd-xsl.tpl"
@@ -175,7 +175,7 @@ class StatsHttpd:
             SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self.cc_session = self.mccs._session
         # read spec file of stats module and subscribe 'Stats'
-        self.stats_module_spec = isc.config.module_spec_from_file(STATS_SPECFILE_LOCATION)
+        self.stats_module_spec = isc.config.module_spec_from_file(SCHEMA_SPECFILE_LOCATION)
         self.stats_config_spec = self.stats_module_spec.get_config_spec()
         self.stats_module_name = self.stats_module_spec.get_module_name()
 
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 818b67a..eccabdc 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -23,7 +23,11 @@ import unittest
 import imp
 from isc.cc.session import Session, SessionError
 from isc.config.ccsession import ModuleCCSession, ModuleCCSessionError
+from fake_time import time, strftime, gmtime
 import stats
+stats.time = time
+stats.strftime = strftime
+stats.gmtime = gmtime
 from stats import SessionSubject, CCSessionListener, get_timestamp, get_datetime
 from fake_time import _TEST_TIME_SECS, _TEST_TIME_STRF
 
@@ -540,9 +544,14 @@ class TestStats2(unittest.TestCase):
                              os.environ["B10_FROM_SOURCE"] + os.sep + \
                                  "src" + os.sep + "bin" + os.sep + "stats" + \
                                  os.sep + "stats.spec")
+            self.assertEqual(stats.SCHEMA_SPECFILE_LOCATION,
+                             os.environ["B10_FROM_SOURCE"] + os.sep + \
+                                 "src" + os.sep + "bin" + os.sep + "stats" + \
+                                 os.sep + "stats-schema.spec")
         imp.reload(stats)
         # change path of SPECFILE_LOCATION
         stats.SPECFILE_LOCATION = TEST_SPECFILE_LOCATION
+        stats.SCHEMA_SPECFILE_LOCATION = TEST_SPECFILE_LOCATION
         self.assertEqual(stats.SPECFILE_LOCATION, TEST_SPECFILE_LOCATION)
         self.subject = stats.SessionSubject(session=self.session, verbose=True)
         self.session = self.subject.session
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 04d04a6..0ccbbb8 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -35,6 +35,8 @@ TEST_MASTER_IPV6_ADDRINFO = (socket.AF_INET6, socket.SOCK_STREAM,
 # If some other process uses this port test will fail.
 TEST_MASTER_PORT = '53535'
 
+TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
+
 soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
                   'master.example.com. admin.example.com ' +
                   '1234 3600 1800 2419200 7200')
@@ -51,6 +53,13 @@ default_answers = [soa_rrset]
 class XfrinTestException(Exception):
     pass
 
+def strip_mutable_tsig_data(data):
+    # Unfortunately we cannot easily compare TSIG RR because we can't tweak
+    # current time.  As a work around this helper function strips off the time
+    # dependent part of TSIG RDATA, i.e., the MAC (assuming HMAC-MD5) and
+    # Time Signed.
+    return data[0:-32] + data[-26:-22] + data[-6:]
+
 class MockXfrin(Xfrin):
     # This is a class attribute of a callable object that specifies a non
     # default behavior triggered in _cc_check_command().  Specific test methods
@@ -60,6 +69,7 @@ class MockXfrin(Xfrin):
     check_command_hook = None
 
     def _cc_setup(self):
+        self._tsig_key_str = None
         pass
 
     def _get_db_file(self):
@@ -196,10 +206,46 @@ class TestXfrinConnection(unittest.TestCase):
                          RRClass.CH())
         c.close()
 
+    def test_send_query(self):
+        def create_msg(query_type):
+            msg = Message(Message.RENDER)
+            query_id = 0x1035
+            msg.set_qid(query_id)
+            msg.set_opcode(Opcode.QUERY())
+            msg.set_rcode(Rcode.NOERROR())
+            query_question = Question(Name("example.com."), RRClass.IN(), query_type)
+            msg.add_question(query_question)
+            return msg
+        self.conn._create_query = create_msg
+        # soa request
+        self.conn._send_query(RRType.SOA())
+        self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\x06\x00\x01')
+        # axfr request
+        self.conn._send_query(RRType.AXFR())
+        self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
+
+        # soa request with tsig
+        self.conn._tsig_ctx = TSIGContext(TSIG_KEY)
+        self.conn._send_query(RRType.SOA())
+        tsig_soa_data = strip_mutable_tsig_data(self.conn.query_data)
+        self.assertEqual(tsig_soa_data, b'\x00n\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x07example\x03com\x00\x00\x06\x00\x01\x07example\x03com\x00\x00\xfa\x00\xff\x00\x00\x00\x00\x00:\x08hmac-md5\x07sig-alg\x03reg\x03int\x00\x01,\x00\x10\x105\x00\x00\x00\x00')
+
+        # axfr request with tsig
+        self.conn._send_query(RRType.AXFR())
+        tsig_axfr_data = strip_mutable_tsig_data(self.conn.query_data)
+        self.assertEqual(tsig_axfr_data, b'\x00n\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x07example\x03com\x00\x00\xfc\x00\x01\x07example\x03com\x00\x00\xfa\x00\xff\x00\x00\x00\x00\x00:\x08hmac-md5\x07sig-alg\x03reg\x03int\x00\x01,\x00\x10\x105\x00\x00\x00\x00')
+
     def test_response_with_invalid_msg(self):
         self.conn.reply_data = b'aaaxxxx'
         self.assertRaises(XfrinTestException, self._handle_xfrin_response)
 
+    def test_response_with_tsig(self):
+        self.conn._tsig_ctx = TSIGContext(TSIG_KEY)
+        # server tsig check fail, return with RCODE 9 (NOTAUTH)
+        self.conn._send_query(RRType.SOA())
+        self.conn.reply_data = self.conn.create_response_data(rcode=Rcode.NOTAUTH())
+        self.assertRaises(XfrinException, self._handle_xfrin_response)
+
     def test_response_without_end_soa(self):
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data()
@@ -399,15 +445,20 @@ class TestXfrinRecorder(unittest.TestCase):
 
 class TestXfrin(unittest.TestCase):
     def setUp(self):
+        # redirect output
+        self.stderr_backup = sys.stderr
+        sys.stderr = open(os.devnull, 'w')
         self.xfr = MockXfrin()
         self.args = {}
         self.args['zone_name'] = TEST_ZONE_NAME
         self.args['port'] = TEST_MASTER_PORT
         self.args['master'] = TEST_MASTER_IPV4_ADDRESS
         self.args['db_file'] = TEST_DB_FILE
+        self.args['tsig_key'] = ''
 
     def tearDown(self):
         self.xfr.shutdown()
+        sys.stderr= self.stderr_backup
 
     def _do_parse_zone_name_class(self):
         return self.xfr._parse_zone_name_and_class(self.args)
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 10a866e..1bf46c1 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -67,15 +67,16 @@ DEFAULT_MASTER = '127.0.0.1'
 def log_error(msg):
     sys.stderr.write("[b10-xfrin] %s\n" % str(msg))
 
-class XfrinException(Exception): 
+class XfrinException(Exception):
     pass
 
 class XfrinConnection(asyncore.dispatcher):
-    '''Do xfrin in this class. '''    
+    '''Do xfrin in this class. '''
 
     def __init__(self,
                  sock_map, zone_name, rrclass, db_file, shutdown_event,
-                 master_addrinfo, verbose = False, idle_timeout = 60): 
+                 master_addrinfo, tsig_key_str = None, verbose = False,
+                 idle_timeout = 60):
         ''' idle_timeout: max idle time for read data from socket.
             db_file: specify the data source file.
             check_soa: when it's true, check soa first before sending xfr query
@@ -93,6 +94,9 @@ class XfrinConnection(asyncore.dispatcher):
         self._shutdown_event = shutdown_event
         self._verbose = verbose
         self._master_address = master_addrinfo[2]
+        self._tsig_ctx = None
+        if tsig_key_str is not None:
+            self._tsig_ctx = TSIGContext(TSIGKey(tsig_key_str))
 
     def connect_to_master(self):
         '''Connect to master in TCP.'''
@@ -130,9 +134,14 @@ class XfrinConnection(asyncore.dispatcher):
 
         msg = self._create_query(query_type)
         render = MessageRenderer()
-        msg.to_wire(render)
-        header_len = struct.pack('H', socket.htons(render.get_length()))
+        # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
+        # we should remove the if statement and use a universal interface later.
+        if self._tsig_ctx is not None:
+            msg.to_wire(render, self._tsig_ctx)
+        else:
+            msg.to_wire(render)
 
+        header_len = struct.pack('H', socket.htons(render.get_length()))
         self._send_data(header_len)
         self._send_data(render.get_data())
 
@@ -142,7 +151,7 @@ class XfrinConnection(asyncore.dispatcher):
         _get_request_response so that we can test the rest of the code without
         involving actual communication with a remote server.'''
         asyncore.loop(self._idle_timeout, map=self._sock_map, count=1)
-    
+
     def _get_request_response(self, size):
         recv_size = 0
         data = b''
@@ -176,7 +185,7 @@ class XfrinConnection(asyncore.dispatcher):
         # strict we should be (see the comment in _check_response_header())
         self._check_response_header(msg)
 
-        # TODO, need select soa record from data source then compare the two 
+        # TODO, need select soa record from data source then compare the two
         # serial, current just return OK, since this function hasn't been used
         # now.
         return XFRIN_OK
@@ -290,14 +299,14 @@ class XfrinConnection(asyncore.dispatcher):
             msg = Message(Message.PARSE)
             msg.from_wire(recvdata)
             self._check_response_status(msg)
-            
+
             answer_section = msg.get_section(Message.SECTION_ANSWER)
             for rr in self._handle_answer_section(answer_section):
                 yield rr
 
             if self._soa_rr_count == 2:
                 break
-            
+
             if self._shutdown_event.is_set():
                 raise XfrinException('xfrin is forced to stop')
 
@@ -322,16 +331,18 @@ class XfrinConnection(asyncore.dispatcher):
             sys.stdout.write('[b10-xfrin] %s\n' % str(msg))
 
 
-def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file, 
-                  shutdown_event, master_addrinfo, check_soa, verbose):
+def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
+                  shutdown_event, master_addrinfo, check_soa, verbose,
+                  tsig_key_str):
     xfrin_recorder.increment(zone_name)
     sock_map = {}
     conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
-                           shutdown_event, master_addrinfo, verbose)
+                           shutdown_event, master_addrinfo,
+                           tsig_key_str, verbose)
     ret = XFRIN_FAIL
     if conn.connect_to_master():
         ret = conn.do_xfrin(check_soa)
-    
+
     # Publish the zone transfer result news, so zonemgr can reset the
     # zone timer, and xfrout can notify the zone's slaves if the result
     # is success.
@@ -379,11 +390,11 @@ class Xfrin:
         self._verbose = verbose
 
     def _cc_setup(self):
-        '''This method is used only as part of initialization, but is 
-        implemented separately for convenience of unit tests; by letting 
-        the test code override this method we can test most of this class 
+        '''This method is used only as part of initialization, but is
+        implemented separately for convenience of unit tests; by letting
+        the test code override this method we can test most of this class
         without requiring a command channel.'''
-        # Create one session for sending command to other modules, because the 
+        # Create one session for sending command to other modules, because the
         # listening session will block the send operation.
         self._send_cc_session = isc.cc.Session()
         self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
@@ -394,15 +405,17 @@ class Xfrin:
         self._max_transfers_in = config_data.get("transfers_in")
         self._master_addr = config_data.get('master_addr') or self._master_addr
         self._master_port = config_data.get('master_port') or self._master_port
+        self._tsig_key_str = config_data.get('tsig_key') or None
 
     def _cc_check_command(self):
-        '''This is a straightforward wrapper for cc.check_command, 
-        but provided as a separate method for the convenience 
+        '''This is a straightforward wrapper for cc.check_command,
+        but provided as a separate method for the convenience
         of unit tests.'''
         self._module_cc.check_command(False)
 
     def config_handler(self, new_config):
         self._max_transfers_in = new_config.get("transfers_in") or self._max_transfers_in
+        self._tsig_key_str = new_config.get('tsig_key') or None
         if ('master_addr' in new_config) or ('master_port' in new_config):
             # User should change the port and address together.
             try:
@@ -420,7 +433,7 @@ class Xfrin:
         return create_answer(0)
 
     def shutdown(self):
-        ''' shutdown the xfrin process. the thread which is doing xfrin should be 
+        ''' shutdown the xfrin process. the thread which is doing xfrin should be
         terminated.
         '''
         self._shutdown_event.set()
@@ -436,30 +449,32 @@ class Xfrin:
             if command == 'shutdown':
                 self._shutdown_event.set()
             elif command == 'notify' or command == REFRESH_FROM_ZONEMGR:
-                # Xfrin receives the refresh/notify command from zone manager. 
-                # notify command maybe has the parameters which 
+                # Xfrin receives the refresh/notify command from zone manager.
+                # notify command maybe has the parameters which
                 # specify the notifyfrom address and port, according the RFC1996, zone
                 # transfer should starts first from the notifyfrom, but now, let 'TODO' it.
                 (zone_name, rrclass) = self._parse_zone_name_and_class(args)
                 (master_addr) = build_addr_info(self._master_addr, self._master_port)
-                ret = self.xfrin_start(zone_name, 
-                                       rrclass, 
+                ret = self.xfrin_start(zone_name,
+                                       rrclass,
                                        self._get_db_file(),
                                        master_addr,
+                                       self._tsig_key_str,
                                        True)
                 answer = create_answer(ret[0], ret[1])
 
             elif command == 'retransfer' or command == 'refresh':
                 # Xfrin receives the retransfer/refresh from cmdctl(sent by bindctl).
-                # If the command has specified master address, do transfer from the 
-                # master address, or else do transfer from the configured masters.                
+                # If the command has specified master address, do transfer from the
+                # master address, or else do transfer from the configured masters.
                 (zone_name, rrclass) = self._parse_zone_name_and_class(args)
                 master_addr = self._parse_master_and_port(args)
                 db_file = args.get('db_file') or self._get_db_file()
-                ret = self.xfrin_start(zone_name, 
-                                       rrclass, 
-                                       db_file, 
+                ret = self.xfrin_start(zone_name,
+                                       rrclass,
+                                       db_file,
                                        master_addr,
+                                       self._tsig_key_str,
                                        (False if command == 'retransfer' else True))
                 answer = create_answer(ret[0], ret[1])
 
@@ -483,14 +498,14 @@ class Xfrin:
                 rrclass = RRClass(rrclass)
             except InvalidRRClass as e:
                 raise XfrinException('invalid RRClass: ' + rrclass)
-        
+
         return zone_name, rrclass
 
     def _parse_master_and_port(self, args):
         port = args.get('port') or self._master_port
         master = args.get('master') or self._master_addr
         return build_addr_info(master, port)
- 
+
     def _get_db_file(self):
         #TODO, the db file path should be got in auth server's configuration
         # if we need access to this configuration more often, we
@@ -506,12 +521,12 @@ class Xfrin:
             db_file = os.environ["B10_FROM_BUILD"] + os.sep + "bind10_zones.sqlite3"
         self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
         return db_file
-       
+
     def publish_xfrin_news(self, zone_name, zone_class,  xfr_result):
         '''Send command to xfrout/zone manager module.
-        If xfrin has finished successfully for one zone, tell the good 
+        If xfrin has finished successfully for one zone, tell the good
         news(command: zone_new_data_ready) to zone manager and xfrout.
-        if xfrin failed, just tell the bad news to zone manager, so that 
+        if xfrin failed, just tell the bad news to zone manager, so that
         it can reset the refresh timer for that zone. '''
         param = {'zone_name': zone_name, 'zone_class': zone_class.to_text()}
         if xfr_result == XFRIN_OK:
@@ -531,8 +546,8 @@ class Xfrin:
                                                                       seq)
                 except isc.cc.session.SessionTimeout:
                     pass        # for now we just ignore the failure
-            except socket.error as err: 
-                log_error("Fail to send message to %s and %s, msgq may has been killed" 
+            except socket.error as err:
+                log_error("Fail to send message to %s and %s, msgq may has been killed"
                           % (XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME))
         else:
             msg = create_command(ZONE_XFRIN_FAILED, param)
@@ -545,14 +560,14 @@ class Xfrin:
                 except isc.cc.session.SessionTimeout:
                     pass        # for now we just ignore the failure
             except socket.error as err:
-                log_error("Fail to send message to %s, msgq may has been killed" 
+                log_error("Fail to send message to %s, msgq may has been killed"
                           % ZONE_MANAGER_MODULE_NAME)
 
     def startup(self):
         while not self._shutdown_event.is_set():
             self._cc_check_command()
 
-    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
+    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo, tsig_key_str,
                     check_soa = True):
         if "pydnspp" not in sys.modules:
             return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
@@ -571,7 +586,8 @@ class Xfrin:
                                                 db_file,
                                                 self._shutdown_event,
                                                 master_addrinfo, check_soa,
-                                                self._verbose))
+                                                self._verbose,
+                                                tsig_key_str))
 
         xfrin_thread.start()
         return (0, 'zone xfrin is started')
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index 61ddaad..46bad69 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -19,6 +19,11 @@
         "item_type": "integer",
         "item_optional": false,
         "item_default": 53
+      },
+      { "item_name": "tsig_key",
+        "item_type": "string",
+        "item_optional": true,
+        "item_default": ""
       }
     ],
     "commands": [
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index c6d151d..479ca68 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -21,11 +21,12 @@ import os
 import tempfile
 from zonemgr import *
 
-ZONE_NAME_CLASS1_IN = ("sd.cn.", "IN")
-ZONE_NAME_CLASS2_CH = ("tw.cn.", "CH")
-ZONE_NAME_CLASS3_IN = ("example.com", "IN")
-ZONE_NAME_CLASS1_CH = ("sd.cn.", "CH")
-ZONE_NAME_CLASS2_IN = ("tw.cn.", "IN")
+ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
+ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
+ZONE_NAME_CLASS2_IN = ("example.org.", "IN")
+ZONE_NAME_CLASS2_CH = ("example.org.", "CH")
+ZONE_NAME_CLASS3_IN = ("example.com.", "IN")
+ZONE_NAME_CLASS3_CH = ("example.com.", "CH")
 
 MAX_TRANSFER_TIMEOUT = 14400
 LOWERBOUND_REFRESH = 10
@@ -80,12 +81,12 @@ class MyZonemgrRefresh(ZonemgrRefresh):
         self._refresh_jitter = 0.25
 
         def get_zone_soa(zone_name, db_file):
-            if zone_name == 'sd.cn.':
-                return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
-                        'a.dns.cn. root.cnnic.cn. 2009073106 7200 3600 2419200 21600')
-            elif zone_name == 'tw.cn.':
-                return (1, 2, 'tw.cn.', 'cn.sd.', 21600, 'SOA', None,
-                        'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600')
+            if zone_name == 'example.net.':
+                return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+                        'a.example.net. root.example.net. 2009073106 7200 3600 2419200 21600')
+            elif zone_name == 'example.org.':
+                return (1, 2, 'example.org.', 'example.org.sd.', 21600, 'SOA', None,
+                        'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600')
             else:
                 return None
         sqlite3_ds.get_zone_soa = get_zone_soa
@@ -94,15 +95,15 @@ class MyZonemgrRefresh(ZonemgrRefresh):
             self._slave_socket, FakeConfig())
         current_time = time.time()
         self._zonemgr_refresh_info = {
-         ('sd.cn.', 'IN'): {
+         ('example.net.', 'IN'): {
          'last_refresh_time': current_time,
          'next_refresh_time': current_time + 6500,
-         'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+         'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
          'zone_state': 0},
-         ('tw.cn.', 'CH'): {
+         ('example.org.', 'CH'): {
          'last_refresh_time': current_time,
          'next_refresh_time': current_time + 6900,
-         'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600',
+         'zone_soa_rdata': 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600',
          'zone_state': 0}
         }
 
@@ -157,6 +158,7 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertFalse(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS2_CH))
         self.assertTrue(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS2_IN))
         self.assertTrue(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS3_IN))
+        self.assertTrue(self.zone_refresh._zone_not_exist(ZONE_NAME_CLASS3_CH))
 
     def test_set_zone_notify_timer(self):
         time1 = time.time()
@@ -179,20 +181,20 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertTrue(self.zone_refresh._zone_is_expired(ZONE_NAME_CLASS1_IN))
 
     def test_get_zone_soa_rdata(self):
-        soa_rdata1  = 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600'
-        soa_rdata2  = 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600'
+        soa_rdata1  = 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600'
+        soa_rdata2  = 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600'
         self.assertEqual(soa_rdata1, self.zone_refresh._get_zone_soa_rdata(ZONE_NAME_CLASS1_IN))
         self.assertRaises(KeyError, self.zone_refresh._get_zone_soa_rdata, ZONE_NAME_CLASS1_CH)
         self.assertEqual(soa_rdata2, self.zone_refresh._get_zone_soa_rdata(ZONE_NAME_CLASS2_CH))
         self.assertRaises(KeyError, self.zone_refresh._get_zone_soa_rdata, ZONE_NAME_CLASS2_IN)
 
     def test_zonemgr_reload_zone(self):
-        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+        soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
         # We need to restore this not to harm other tests
         old_get_zone_soa = sqlite3_ds.get_zone_soa
         def get_zone_soa(zone_name, db_file):
-            return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
-                    'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+            return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+                    'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
         sqlite3_ds.get_zone_soa = get_zone_soa
 
         self.zone_refresh.zonemgr_reload_zone(ZONE_NAME_CLASS1_IN)
@@ -274,15 +276,15 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertTrue(self.zone_refresh._zone_mgr_is_empty())
 
     def test_zonemgr_add_zone(self):
-        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+        soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
         # This needs to be restored. The following test actually failed if we left
         # this unclean
         old_get_zone_soa = sqlite3_ds.get_zone_soa
         time1 = time.time()
 
         def get_zone_soa(zone_name, db_file):
-            return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
-                    'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+            return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+                    'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
 
         sqlite3_ds.get_zone_soa = get_zone_soa
 
@@ -314,15 +316,15 @@ class TestZonemgrRefresh(unittest.TestCase):
         current_time = time.time()
         self.assertTrue(zone_timeout <= current_time)
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_handle_notify,\
-                          ("org.cn.", "IN"), "127.0.0.1")
+                          ZONE_NAME_CLASS3_CH, "127.0.0.1")
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_handle_notify,\
                           ZONE_NAME_CLASS3_IN, "127.0.0.1")
 
     def test_zone_refresh_success(self):
-        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
+        soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
         def get_zone_soa(zone_name, db_file):
-            return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
-                    'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
+            return (1, 2, 'example.net.', 'example.net.sd.', 21600, 'SOA', None,
+                    'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600')
         sqlite3_ds.get_zone_soa = get_zone_soa
         time1 = time.time()
         self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
@@ -337,11 +339,11 @@ class TestZonemgrRefresh(unittest.TestCase):
         last_refresh_time = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["last_refresh_time"]
         self.assertTrue(time1 <= last_refresh_time)
         self.assertTrue(last_refresh_time <= time2)
-        self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ("org.cn.", "CH"))
+        self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ("example.test.", "CH"))
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_success, ZONE_NAME_CLASS3_IN)
 
     def test_zone_refresh_fail(self):
-        soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600'
+        soa_rdata = 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600'
         time1 = time.time()
         self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
         self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
@@ -357,22 +359,22 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
         self.assertEqual(ZONE_EXPIRED, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"])
 
-        self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ("org.cn.", "CH"))
+        self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_CH)
         self.assertRaises(ZonemgrException, self.zone_refresh.zone_refresh_fail, ZONE_NAME_CLASS3_IN)
 
     def test_find_need_do_refresh_zone(self):
         time1 = time.time()
         self.zone_refresh._zonemgr_refresh_info = {
-                ("sd.cn.","IN"):{
+                ("example.net.","IN"):{
                     'last_refresh_time': time1,
                     'next_refresh_time': time1 + 7200,
-                    'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+                    'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
                     'zone_state': ZONE_OK},
-                ("tw.cn.","CH"):{
+                ("example.org.","CH"):{
                     'last_refresh_time': time1 - 7200,
                     'next_refresh_time': time1,
                     'refresh_timeout': time1 + MAX_TRANSFER_TIMEOUT,
-                    'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600',
+                    'zone_soa_rdata': 'a.example.org. root.example.org. 2009073112 7200 3600 2419200 21600',
                     'zone_state': ZONE_REFRESHING}
                 }
         zone_need_refresh = self.zone_refresh._find_need_do_refresh_zone()
@@ -385,10 +387,10 @@ class TestZonemgrRefresh(unittest.TestCase):
     def test_do_refresh(self):
         time1 = time.time()
         self.zone_refresh._zonemgr_refresh_info = {
-                ("sd.cn.", "IN"):{
+                ("example.net.", "IN"):{
                     'last_refresh_time': time1 - 7200,
                     'next_refresh_time': time1 - 1,
-                    'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+                    'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
                     'zone_state': ZONE_OK}
                 }
         self.zone_refresh._do_refresh(ZONE_NAME_CLASS1_IN)
@@ -416,10 +418,10 @@ class TestZonemgrRefresh(unittest.TestCase):
         """
         time1 = time.time()
         self.zone_refresh._zonemgr_refresh_info = {
-                ("sd.cn.", "IN"):{
+                ("example.net.", "IN"):{
                     'last_refresh_time': time1 - 7200,
                     'next_refresh_time': time1 - 1,
-                    'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
+                    'zone_soa_rdata': 'a.example.net. root.example.net. 2009073105 7200 3600 2419200 21600',
                     'zone_state': ZONE_OK}
                 }
         self.zone_refresh._check_sock = self.zone_refresh._master_socket
@@ -465,19 +467,19 @@ class TestZonemgrRefresh(unittest.TestCase):
         # Put something in
         config.set_zone_list_from_name_classes([ZONE_NAME_CLASS1_IN])
         self.zone_refresh.update_config_data(config)
-        self.assertTrue(("sd.cn.", "IN") in
+        self.assertTrue(("example.net.", "IN") in
                         self.zone_refresh._zonemgr_refresh_info)
         # This one does not exist
         config.set_zone_list_from_name_classes(["example.net", "CH"])
         self.assertRaises(ZonemgrException,
                           self.zone_refresh.update_config_data, config)
         # So it should not affect the old ones
-        self.assertTrue(("sd.cn.", "IN") in
+        self.assertTrue(("example.net.", "IN") in
                         self.zone_refresh._zonemgr_refresh_info)
         # Make sure it works even when we "accidentally" forget the final dot
-        config.set_zone_list_from_name_classes([("sd.cn", "IN")])
+        config.set_zone_list_from_name_classes([("example.net", "IN")])
         self.zone_refresh.update_config_data(config)
-        self.assertTrue(("sd.cn.", "IN") in
+        self.assertTrue(("example.net.", "IN") in
                         self.zone_refresh._zonemgr_refresh_info)
 
     def tearDown(self):
@@ -532,7 +534,7 @@ class TestZonemgr(unittest.TestCase):
         self.assertEqual(self.zonemgr.config_handler(config_data1),
                          {"result": [0]})
         self.assertEqual(config_data1, self.zonemgr._config_data)
-        config_data2 = {"zone_name" : "sd.cn.", "port" : "53", "master" : "192.168.1.1"}
+        config_data2 = {"zone_name" : "example.net.", "port" : "53", "master" : "192.168.1.1"}
         self.zonemgr.config_handler(config_data2)
         self.assertEqual(config_data1, self.zonemgr._config_data)
         # jitter should not be bigger than half of the original value
@@ -553,11 +555,11 @@ class TestZonemgr(unittest.TestCase):
         self.assertEqual("initdb.file", self.zonemgr.get_db_file())
 
     def test_parse_cmd_params(self):
-        params1 = {"zone_name" : "org.cn", "zone_class" : "CH", "master" : "127.0.0.1"}
-        answer1 = (("org.cn", "CH"), "127.0.0.1")
+        params1 = {"zone_name" : "example.com.", "zone_class" : "CH", "master" : "127.0.0.1"}
+        answer1 = (ZONE_NAME_CLASS3_CH, "127.0.0.1")
         self.assertEqual(answer1, self.zonemgr._parse_cmd_params(params1, ZONE_NOTIFY_COMMAND))
-        params2 = {"zone_name" : "org.cn", "zone_class" : "CH"}
-        answer2 = ("org.cn", "CH")
+        params2 = {"zone_name" : "example.com.", "zone_class" : "IN"}
+        answer2 = ZONE_NAME_CLASS3_IN
         self.assertEqual(answer2, self.zonemgr._parse_cmd_params(params2, ZONE_XFRIN_SUCCESS_COMMAND))
         self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
         params1 = {"zone_class" : "CH"}
diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
index 5e6d81f..4659dc4 100644
--- a/src/cppcheck-suppress.lst
+++ b/src/cppcheck-suppress.lst
@@ -12,4 +12,4 @@ functionConst:src/lib/cache/rrset_cache.h
 // Intentional self assignment tests.  Suppress warning about them.
 selfAssignment:src/lib/dns/tests/name_unittest.cc:293
 selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
-selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:120
+selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:125
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 184b090..81ddd18 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = exceptions util cryptolink dns cc config python xfr bench \
-          log asiolink asiodns nsas cache resolve testutils datasrc \
+SUBDIRS = exceptions util log cryptolink dns cc config python xfr \
+          bench asiolink asiodns nsas cache resolve testutils datasrc \
           server_common
diff --git a/src/lib/asiodns/Makefile.am b/src/lib/asiodns/Makefile.am
index 7beaaa3..2a6c3ac 100644
--- a/src/lib/asiodns/Makefile.am
+++ b/src/lib/asiodns/Makefile.am
@@ -11,8 +11,8 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 CLEANFILES = *.gcno *.gcda asiodef.h asiodef.cc
 
 # Define rule to build logging source files from message file
-asiodef.h asiodef.cc: asiodef.msg
-	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/asiodns/asiodef.msg
+asiodef.h asiodef.cc: asiodef.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/asiodns/asiodef.mes
 
 BUILT_SOURCES = asiodef.h asiodef.cc
 
@@ -28,7 +28,7 @@ libasiodns_la_SOURCES += io_fetch.cc io_fetch.h
 
 nodist_libasiodns_la_SOURCES = asiodef.cc asiodef.h
 
-EXTRA_DIST = asiodef.msg
+EXTRA_DIST = asiodef.mes
 
 # Note: the ordering matters: -Wno-... must follow -Wextra (defined in
 # B10_CXXFLAGS)
diff --git a/src/lib/asiodns/asiodef.mes b/src/lib/asiodns/asiodef.mes
new file mode 100644
index 0000000..3f2e80c
--- /dev/null
+++ b/src/lib/asiodns/asiodef.mes
@@ -0,0 +1,56 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX ASIODNS_
+$NAMESPACE isc::asiodns
+
+% FETCHCOMP   upstream fetch to %1(%2) has now completed
+A debug message, this records the the upstream fetch (a query made by the
+resolver on behalf of its client) to the specified address has completed.
+
+% FETCHSTOP   upstream fetch to %1(%2) has been stopped
+An external component has requested the halting of an upstream fetch.  This
+is an allowed operation, and the message should only appear if debug is
+enabled.
+
+% OPENSOCK    error %1 opening %2 socket to %3(%4)
+The asynchronous I/O code encountered an error when trying to open a socket
+of the specified protocol in order to send a message to the target address.
+The the number of the system error that cause the problem is given in the
+message.
+
+% RECVSOCK    error %1 reading %2 data from %3(%4)
+The asynchronous I/O code encountered an error when trying read data from
+the specified address on the given protocol.  The the number of the system
+error that cause the problem is given in the message.
+
+% SENDSOCK    error %1 sending data using %2 to %3(%4)
+The asynchronous I/O code encountered an error when trying send data to
+the specified address on the given protocol.  The the number of the system
+error that cause the problem is given in the message.
+
+% RECVTMO     receive timeout while waiting for data from %1(%2)
+An upstream fetch from the specified address timed out.  This may happen for
+any number of reasons and is most probably a problem at the remote server
+or a problem on the network.  The message will only appear if debug is
+enabled.
+
+% UNKORIGIN  unknown origin for ASIO error code %1 (protocol: %2, address %3)
+This message should not appear and indicates an internal error if it does.
+Please enter a bug report.
+
+% UNKRESULT  unknown result (%1) when IOFetch::stop() was executed for I/O to %2(%3)
+The termination method of the resolver's upstream fetch class was called with
+an unknown result code (which is given in the message).  This message should
+not appear and may indicate an internal error.  Please enter a bug report.
diff --git a/src/lib/asiodns/asiodef.msg b/src/lib/asiodns/asiodef.msg
deleted file mode 100644
index ea3ec2f..0000000
--- a/src/lib/asiodns/asiodef.msg
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-$PREFIX ASIODNS_
-$NAMESPACE isc::asiodns
-
-FETCHCOMP   upstream fetch to %1(%2) has now completed
-+ A debug message, this records the the upstream fetch (a query made by the
-+ resolver on behalf of its client) to the specified address has completed.
-
-FETCHSTOP   upstream fetch to %1(%2) has been stopped
-+ An external component has requested the halting of an upstream fetch.  This
-+ is an allowed operation, and the message should only appear if debug is
-+ enabled.
-
-OPENSOCK    error %1 opening %2 socket to %3(%4)
-+ The asynchronous I/O code encountered an error when trying to open a socket
-+ of the specified protocol in order to send a message to the target address.
-+ The the number of the system error that cause the problem is given in the
-+ message.
-
-RECVSOCK    error %1 reading %2 data from %3(%4)
-+ The asynchronous I/O code encountered an error when trying read data from
-+ the specified address on the given protocol.  The the number of the system
-+ error that cause the problem is given in the message.
-
-SENDSOCK    error %1 sending data using %2 to %3(%4)
-+ The asynchronous I/O code encountered an error when trying send data to
-+ the specified address on the given protocol.  The the number of the system
-+ error that cause the problem is given in the message.
-
-RECVTMO     receive timeout while waiting for data from %1(%2)
-+ An upstream fetch from the specified address timed out.  This may happen for
-+ any number of reasons and is most probably a problem at the remote server
-+ or a problem on the network.  The message will only appear if debug is
-+ enabled.
-
-UNKORIGIN  unknown origin for ASIO error code %1 (protocol: %2, address %3)
-+ This message should not appear and indicates an internal error if it does.
-+ Please enter a bug report.
-
-UNKRESULT  unknown result (%1) when IOFetch::stop() was executed for I/O to %2(%3)
-+ The termination method of the resolver's upstream fetch class was called with
-+ an unknown result code (which is given in the message).  This message should
-+ not appear and may indicate an internal error.  Please enter a bug report.
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index cc8bd11..e535381 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/src/lib/asiodns/io_fetch.cc
@@ -35,7 +35,6 @@
 #include <asiolink/udp_endpoint.h>
 #include <asiolink/udp_socket.h>
 
-#include <dns/message.h>
 #include <dns/messagerenderer.h>
 #include <dns/opcode.h>
 #include <dns/rcode.h>
@@ -91,7 +90,6 @@ struct IOFetchData {
                                              ///< Socket to use for I/O
     boost::scoped_ptr<IOEndpoint> remote_snd;///< Where the fetch is sent
     boost::scoped_ptr<IOEndpoint> remote_rcv;///< Where the response came from
-    isc::dns::Question          question;    ///< Question to be asked
     OutputBufferPtr   msgbuf;      ///< Wire buffer for question
     OutputBufferPtr   received;    ///< Received data put here
     IOFetch::Callback*          callback;    ///< Called on I/O Completion
@@ -121,7 +119,6 @@ struct IOFetchData {
     /// \param proto Either IOFetch::TCP or IOFetch::UDP.
     /// \param service I/O Service object to handle the asynchronous
     ///        operations.
-    /// \param query DNS question to send to the upstream server.
     /// \param address IP address of upstream server
     /// \param port Port to use for the query
     /// \param buff Output buffer into which the response (in wire format)
@@ -133,8 +130,8 @@ struct IOFetchData {
     ///
     /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554)
     IOFetchData(IOFetch::Protocol proto, IOService& service,
-        const isc::dns::Question& query, const IOAddress& address,
-        uint16_t port, OutputBufferPtr& buff, IOFetch::Callback* cb, int wait)
+        const IOAddress& address, uint16_t port, OutputBufferPtr& buff,
+        IOFetch::Callback* cb, int wait)
         :
         socket((proto == IOFetch::UDP) ?
             static_cast<IOAsioSocket<IOFetch>*>(
@@ -150,7 +147,6 @@ struct IOFetchData {
             static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
             static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
             ),
-        question(query),
         msgbuf(new OutputBuffer(512)),
         received(buff),
         callback(cb),
@@ -185,10 +181,10 @@ struct IOFetchData {
 IOFetch::IOFetch(Protocol protocol, IOService& service,
     const isc::dns::Question& question, const IOAddress& address, uint16_t port,
     OutputBufferPtr& buff, Callback* cb, int wait)
-    :
-    data_(new IOFetchData(protocol, service, question, address,
-        port, buff, cb, wait))
 {
+    MessagePtr query_msg(new Message(Message::RENDER));
+    initIOFetch(query_msg, protocol, service, question, address, port, buff,
+                cb, wait);
 }
 
 IOFetch::IOFetch(Protocol protocol, IOService& service,
@@ -196,14 +192,56 @@ IOFetch::IOFetch(Protocol protocol, IOService& service,
     OutputBufferPtr& buff, Callback* cb, int wait)
     :
     data_(new IOFetchData(protocol, service,
-          isc::dns::Question(isc::dns::Name("dummy.example.org"),
-                             isc::dns::RRClass::IN(), isc::dns::RRType::A()),
           address, port, buff, cb, wait))
 {
     data_->msgbuf = outpkt;
     data_->packet = true;
 }
 
+IOFetch::IOFetch(Protocol protocol, IOService& service,
+    ConstMessagePtr query_message, const IOAddress& address, uint16_t port,
+    OutputBufferPtr& buff, Callback* cb, int wait)
+{
+    MessagePtr msg(new Message(Message::RENDER));
+
+    msg->setHeaderFlag(Message::HEADERFLAG_RD,
+                       query_message->getHeaderFlag(Message::HEADERFLAG_RD));
+    msg->setHeaderFlag(Message::HEADERFLAG_CD,
+                       query_message->getHeaderFlag(Message::HEADERFLAG_CD));
+
+    ConstEDNSPtr edns(query_message->getEDNS());
+    const bool dnssec_ok = edns && edns->getDNSSECAwareness();
+    if (edns) {
+        EDNSPtr edns_response(new EDNS());
+        edns_response->setDNSSECAwareness(dnssec_ok);
+        // TODO: We should make our own edns bufsize length configurable
+        edns_response->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
+        msg->setEDNS(edns_response);
+    }
+
+    initIOFetch(msg, protocol, service,
+                **(query_message->beginQuestion()),
+                address, port, buff, cb, wait);
+}
+
+void
+IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol, IOService& service,
+                     const isc::dns::Question& question,
+                     const IOAddress& address, uint16_t port,
+                     OutputBufferPtr& buff, Callback* cb, int wait)
+{
+    data_ = boost::shared_ptr<IOFetchData>(new IOFetchData(
+        protocol, service, address, port, buff, cb, wait));
+
+    query_msg->setQid(data_->qid);
+    query_msg->setOpcode(Opcode::QUERY());
+    query_msg->setRcode(Rcode::NOERROR());
+    query_msg->setHeaderFlag(Message::HEADERFLAG_RD);
+    query_msg->addQuestion(question);
+    MessageRenderer renderer(*data_->msgbuf);
+    query_msg->toWire(renderer);
+}
+
 // Return protocol in use.
 
 IOFetch::Protocol
@@ -235,17 +273,7 @@ IOFetch::operator()(asio::error_code ec, size_t length) {
                 // first two bytes of the packet).
                 data_->msgbuf->writeUint16At(data_->qid, 0);
 
-            } else {
-                // A question was given, construct the packet
-                Message msg(Message::RENDER);
-                msg.setQid(data_->qid);
-                msg.setOpcode(Opcode::QUERY());
-                msg.setRcode(Rcode::NOERROR());
-                msg.setHeaderFlag(Message::HEADERFLAG_RD);
-                msg.addQuestion(data_->question);
-                MessageRenderer renderer(*data_->msgbuf);
-                msg.toWire(renderer);
-            }
+            } 
         }
 
         // If we timeout, we stop, which will can cancel outstanding I/Os and
@@ -406,4 +434,3 @@ void IOFetch::logIOFailure(asio::error_code ec) {
 
 } // namespace asiodns
 } // namespace isc {
-
diff --git a/src/lib/asiodns/io_fetch.h b/src/lib/asiodns/io_fetch.h
index 98c917d..9626ffe 100644
--- a/src/lib/asiodns/io_fetch.h
+++ b/src/lib/asiodns/io_fetch.h
@@ -29,6 +29,7 @@
 
 #include <util/buffer.h>
 #include <dns/question.h>
+#include <dns/message.h>
 
 namespace isc {
 namespace asiodns {
@@ -136,6 +137,20 @@ public:
         uint16_t port, isc::util::OutputBufferPtr& buff, Callback* cb,
         int wait = -1);
 
+    /// \brief Constructor
+    ///  This constructor has one parameter "query_message", which
+    ///  is the shared_ptr to a full query message. It's different
+    ///  with above contructor which has only question section. All
+    ///  other parameters are same.
+    ///
+    /// \param query_message the shared_ptr to a full query message
+    ///        got from a query client.
+    IOFetch(Protocol protocol, isc::asiolink::IOService& service,
+        isc::dns::ConstMessagePtr query_message,
+        const isc::asiolink::IOAddress& address,
+        uint16_t port, isc::util::OutputBufferPtr& buff, Callback* cb,
+        int wait = -1);
+
     /// \brief Constructor.
     ///
     /// Creates the object that will handle the upstream fetch.
@@ -184,6 +199,15 @@ public:
     void stop(Result reason = STOPPED);
 
 private:
+    /// \brief IOFetch Initialization Function.
+    /// All the parameters are same with the constructor, except
+    /// parameter "query_message"
+    /// \param query_message the message to be sent out.
+    void initIOFetch(isc::dns::MessagePtr& query_message, Protocol protocol,
+            isc::asiolink::IOService& service, const isc::dns::Question& question,
+            const isc::asiolink::IOAddress& address, uint16_t port,
+            isc::util::OutputBufferPtr& buff, Callback* cb, int wait);
+
     /// \brief Log I/O Failure
     ///
     /// Records an I/O failure to the log file
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index bfdf7c1..c0d7af6 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -10,6 +10,12 @@ if USE_STATIC_LINK
 AM_LDFLAGS = -static
 endif
 
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
 CLEANFILES = *.gcno *.gcda
 
 TESTS =
diff --git a/src/lib/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
index c24e60e..8e8ef81 100644
--- a/src/lib/asiolink/tests/interval_timer_unittest.cc
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -18,7 +18,7 @@
 #include <asio.hpp>
 #include <asiolink/asiolink.h>
 
-#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 
 namespace {
 // TODO: Consider this margin
@@ -166,16 +166,22 @@ TEST_F(IntervalTimerTest, startIntervalTimer) {
     io_service_.run();
     // reaches here after timer expired
     // delta: difference between elapsed time and 100 milliseconds.
+    boost::posix_time::time_duration test_runtime =
+        boost::posix_time::microsec_clock::universal_time() - start;
+    EXPECT_FALSE(test_runtime.is_negative()) << 
+                 "test duration " << test_runtime << 
+                 " negative - clock skew?";
     boost::posix_time::time_duration delta =
-        (boost::posix_time::microsec_clock::universal_time() - start)
-         - boost::posix_time::millisec(100);
+        test_runtime - boost::posix_time::milliseconds(100);
     if (delta.is_negative()) {
         delta.invert_sign();
     }
     // expect TimerCallBack is called; timer_called_ is true
     EXPECT_TRUE(timer_called_);
     // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
-    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC) << 
+                "delta " << delta.total_milliseconds() << "msec " <<
+                ">= " << TIMER_MARGIN_MSEC.total_milliseconds();
 }
 
 TEST_F(IntervalTimerTest, destructIntervalTimer) {
@@ -283,14 +289,20 @@ TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
     //   + 400 milliseconds for TimerCallBackOverwriter (stop)
     //   = 800 milliseconds.
     // delta: difference between elapsed time and 400 + 100 milliseconds
+    boost::posix_time::time_duration test_runtime =
+        boost::posix_time::microsec_clock::universal_time() - start;
+    EXPECT_FALSE(test_runtime.is_negative()) << 
+                 "test duration " << test_runtime << 
+                 " negative - clock skew?";
     boost::posix_time::time_duration delta =
-        (boost::posix_time::microsec_clock::universal_time() - start)
-         - boost::posix_time::millisec(400 + 100);
+        test_runtime - boost::posix_time::milliseconds(400 + 100);
     if (delta.is_negative()) {
         delta.invert_sign();
     }
     // expect callback function is updated: TimerCallBack is called
     EXPECT_TRUE(timer_called_);
     // expect interval is updated
-    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC) << 
+                "delta " << delta.total_milliseconds() << " msec " <<
+                ">= " << TIMER_MARGIN_MSEC.total_milliseconds();
 }
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
index 99e8c86..52337ad 100644
--- a/src/lib/config/Makefile.am
+++ b/src/lib/config/Makefile.am
@@ -2,9 +2,24 @@ SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 
+# Define rule to build logging source files from message file
+configdef.h configdef.cc: configdef.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/config/configdef.mes
+
+BUILT_SOURCES = configdef.h configdef.cc
+
 lib_LTLIBRARIES = libcfgclient.la
-libcfgclient_la_SOURCES = config_data.h config_data.cc module_spec.h module_spec.cc ccsession.cc ccsession.h
+libcfgclient_la_SOURCES = config_data.h config_data.cc
+libcfgclient_la_SOURCES += module_spec.h module_spec.cc
+libcfgclient_la_SOURCES += ccsession.cc ccsession.h
+libcfgclient_la_SOURCES += config_log.h config_log.cc
+
+nodist_libcfgclient_la_SOURCES  = configdef.h configdef.cc
+
+# The message file should be in the distribution.
+EXTRA_DIST = configdef.mes
 
-CLEANFILES = *.gcno *.gcda
+CLEANFILES = *.gcno *.gcda configdef.h configdef.cc
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 69621a4..45710e3 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -12,12 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-// 
-// todo: generalize this and make it into a specific API for all modules
-//       to use (i.e. connect to cc, send config and commands, get config,
-//               react on config change announcements)
-//
-
 #include <config.h>
 
 #include <stdexcept>
@@ -38,6 +32,7 @@
 #include <cc/session.h>
 #include <exceptions/exceptions.h>
 
+#include <config/config_log.h>
 #include <config/ccsession.h>
 
 using namespace std;
@@ -164,18 +159,18 @@ ModuleCCSession::readModuleSpecification(const std::string& filename) {
     // this file should be declared in a @something@ directive
     file.open(filename.c_str());
     if (!file) {
-        cout << "error opening " << filename << ": " << strerror(errno) << endl;
-        exit(1);
+        LOG_ERROR(config_logger, CONFIG_FOPEN_ERR).arg(filename).arg(strerror(errno));
+        isc_throw(CCSessionInitError, strerror(errno));
     }
 
     try {
         module_spec = moduleSpecFromFile(file, true);
     } catch (const JSONError& pe) {
-        cout << "Error parsing module specification file: " << pe.what() << endl;
-        exit(1);
+        LOG_ERROR(config_logger, CONFIG_JSON_PARSE).arg(filename).arg(pe.what());
+        isc_throw(CCSessionInitError, pe.what());
     } catch (const ModuleSpecError& dde) {
-        cout << "Error reading module specification file: " << dde.what() << endl;
-        exit(1);
+        LOG_ERROR(config_logger, CONFIG_MODULE_SPEC).arg(filename).arg(dde.what());
+        isc_throw(CCSessionInitError, dde.what());
     }
     file.close();
     return (module_spec);
@@ -223,7 +218,8 @@ ModuleCCSession::ModuleCCSession(
     int rcode;
     ConstElementPtr err = parseAnswer(rcode, answer);
     if (rcode != 0) {
-        std::cerr << "[" << module_name_ << "] Error in specification: " << answer << std::endl;
+        LOG_ERROR(config_logger, CONFIG_MANAGER_MOD_SPEC).arg(answer->str());
+        isc_throw(CCSessionInitError, answer->str());
     }
     
     setLocalConfig(Element::fromJSON("{}"));
@@ -236,7 +232,8 @@ ModuleCCSession::ModuleCCSession(
         if (rcode == 0) {
             handleConfigUpdate(new_config);
         } else {
-            std::cerr << "[" << module_name_ << "] Error getting config: " << new_config << std::endl;
+            LOG_ERROR(config_logger, CONFIG_MANAGER_CONFIG).arg(new_config->str());
+            isc_throw(CCSessionInitError, answer->str());
         }
     }
 
@@ -348,24 +345,51 @@ ModuleCCSession::checkCommand() {
                 answer = checkModuleCommand(cmd_str, target_module, arg);
             }
         } catch (const CCSessionError& re) {
-            // TODO: Once we have logging and timeouts, we should not
-            // answer here (potential interference)
-            answer = createAnswer(1, re.what());
+            LOG_ERROR(config_logger, CONFIG_CCSESSION_MSG).arg(re.what());
         }
         if (!isNull(answer)) {
             session_.reply(routing, answer);
         }
     }
-    
+
     return (0);
 }
 
 std::string
-ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
-    ModuleSpec rmod_spec = readModuleSpecification(spec_file_name);
-    std::string module_name = rmod_spec.getFullSpec()->get("module_name")->stringValue();
+ModuleCCSession::addRemoteConfig(const std::string& spec_name,
+                                 void (*handler)(const std::string& module,
+                                          ConstElementPtr),
+                                 bool spec_is_filename)
+{
+    std::string module_name;
+    ModuleSpec rmod_spec;
+    if (spec_is_filename) {
+        // It's a file name, so load it
+        rmod_spec = readModuleSpecification(spec_name);
+        module_name =
+            rmod_spec.getFullSpec()->get("module_name")->stringValue();
+    } else {
+        // It's module name, request it from config manager
+        ConstElementPtr cmd = Element::fromJSON("{ \"command\": ["
+                                                "\"get_module_spec\","
+                                                "{\"module_name\": \"" +
+                                                module_name + "\"} ] }");
+        unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
+        ConstElementPtr env, answer;
+        session_.group_recvmsg(env, answer, false, seq);
+        int rcode;
+        ConstElementPtr spec_data = parseAnswer(rcode, answer);
+        if (rcode == 0 && spec_data) {
+            rmod_spec = ModuleSpec(spec_data);
+            module_name = spec_name;
+            if (module_name != rmod_spec.getModuleName()) {
+                isc_throw(CCSessionError, "Module name mismatch");
+            }
+        } else {
+            isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
+        }
+    }
     ConfigData rmod_config = ConfigData(rmod_spec);
-    session_.subscribe(module_name);
 
     // Get the current configuration values for that module
     ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
@@ -375,8 +399,9 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
     session_.group_recvmsg(env, answer, false, seq);
     int rcode;
     ConstElementPtr new_config = parseAnswer(rcode, answer);
+    ElementPtr local_config;
     if (rcode == 0 && new_config) {
-        ElementPtr local_config = rmod_config.getLocalConfig();
+        local_config = rmod_config.getLocalConfig();
         isc::data::merge(local_config, new_config);
         rmod_config.setLocalConfig(local_config);
     } else {
@@ -385,6 +410,11 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
 
     // all ok, add it
     remote_module_configs_[module_name] = rmod_config;
+    if (handler) {
+        remote_module_handlers_[module_name] = handler;
+        handler(module_name, local_config);
+    }
+    session_.subscribe(module_name);
     return (module_name);
 }
 
@@ -395,6 +425,7 @@ ModuleCCSession::removeRemoteConfig(const std::string& module_name) {
     it = remote_module_configs_.find(module_name);
     if (it != remote_module_configs_.end()) {
         remote_module_configs_.erase(it);
+        remote_module_handlers_.erase(module_name);
         session_.unsubscribe(module_name);
     }
 }
@@ -424,6 +455,11 @@ ModuleCCSession::updateRemoteConfig(const std::string& module_name,
     if (it != remote_module_configs_.end()) {
         ElementPtr rconf = (*it).second.getLocalConfig();
         isc::data::merge(rconf, new_config);
+        std::map<std::string, RemoteHandler>::iterator hit =
+            remote_module_handlers_.find(module_name);
+        if (hit != remote_module_handlers_.end()) {
+            hit->second(module_name, new_config);
+        }
     }
 }
 
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index d8d1eeb..c845b8f 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -135,6 +135,15 @@ public:
 };
 
 ///
+/// \brief This exception is thrown if the constructor fails
+///
+class CCSessionInitError : public isc::Exception {
+public:
+    CCSessionInitError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+///
 /// \brief This module keeps a connection to the command channel,
 /// holds configuration information, and handles messages from
 /// the command channel
@@ -154,6 +163,11 @@ public:
      * AbstractSession without establishing the session.
      * Note: the design decision on who is responsible for establishing the
      * session is in flux, and may change in near future.
+     *
+     * \exception CCSessionInitError when the initialization fails,
+     *            either because the file cannot be read or there is
+     *            a communication problem with the config manager.
+     *
      * @param command_handler A callback function pointer to be called when
      * a control command from a remote agent needs to be performed on the
      * local module.
@@ -220,24 +234,43 @@ public:
     /**
      * Gives access to the configuration values of a different module
      * Once this function has been called with the name of the specification
-     * file of the module you want the configuration of, you can use
+     * file or the module you want the configuration of, you can use
      * \c getRemoteConfigValue() to get a specific setting.
-     * Changes are automatically updated, but you cannot specify handlers
-     * for those changes, must use \c getRemoteConfigValue() to get a value
-     * This function will subscribe to the relevant module channel.
+     * Changes are automatically updated, and you can specify handlers
+     * for those changes. This function will subscribe to the relevant module
+     * channel.
      *
-     * \param spec_file_name The path to the specification file of
-     *                       the module we want to have configuration
-     *                       values from
+     * \param spec_name This specifies the module to add. It is either a
+     *                  filename of the spec file to use or a name of module
+     *                  (in case it's a module name, the spec data is
+     *                  downloaded from the configuration manager, therefore
+     *                  the configuration manager must know it). If
+     *                  spec_is_filenabe is true (the default), then a
+     *                  filename is assumed, otherwise a module name.
+     * \param handler The handler function called whenever there's a change.
+     *                Called once initally from this function. May be NULL
+     *                if you don't want any handler to be called and you're
+     *                fine with requesting the data through
+     *                getRemoteConfigValue() each time.
+     *
+     *                The handler should not throw, or it'll fall trough and
+     *                the exception will get into strange places, probably
+     *                aborting the application.
+     * \param spec_is_filename Says if spec_name is filename or module name.
      * \return The name of the module specified in the given specification
      *         file
      */
-    std::string addRemoteConfig(const std::string& spec_file_name);
+    std::string addRemoteConfig(const std::string& spec_name,
+                                void (*handler)(const std::string& module_name,
+                                                isc::data::ConstElementPtr
+                                                update) = NULL,
+                                bool spec_is_filename = true);
 
     /**
      * Removes the module with the given name from the remote config
      * settings. If the module was not added with \c addRemoteConfig(),
-     * nothing happens.
+     * nothing happens. If there was a handler for this config, it is
+     * removed as well.
      */
     void removeRemoteConfig(const std::string& module_name);
 
@@ -282,7 +315,11 @@ private:
         const std::string& command,
         isc::data::ConstElementPtr args);
 
+    typedef void (*RemoteHandler)(const std::string&,
+                                  isc::data::ConstElementPtr);
     std::map<std::string, ConfigData> remote_module_configs_;
+    std::map<std::string, RemoteHandler> remote_module_handlers_;
+
     void updateRemoteConfig(const std::string& module_name,
                             isc::data::ConstElementPtr new_config);
 };
diff --git a/src/lib/config/config_log.cc b/src/lib/config/config_log.cc
new file mode 100644
index 0000000..672b9f1
--- /dev/null
+++ b/src/lib/config/config_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the config lib
+
+#include "config/config_log.h"
+
+namespace isc {
+namespace config {
+
+isc::log::Logger config_logger("config");
+
+} // namespace nsas
+} // namespace isc
+
diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h
new file mode 100644
index 0000000..22e5a5c
--- /dev/null
+++ b/src/lib/config/config_log.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __CONFIG_LOG__H
+#define __CONFIG_LOG__H
+
+#include <log/macros.h>
+#include "configdef.h"
+
+namespace isc {
+namespace config {
+
+/// \brief Config Logging
+///
+/// Defines logger object for config log messages
+
+/// \brief Config Logger
+///
+/// Define the logger used to log messages.  We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger config_logger;    // isc::config::config_logger is the CONFIG logger
+
+} // namespace config
+} // namespace isc
+
+#endif // __CONFIG_LOG__H
diff --git a/src/lib/config/configdef.mes b/src/lib/config/configdef.mes
new file mode 100644
index 0000000..4c3c991
--- /dev/null
+++ b/src/lib/config/configdef.mes
@@ -0,0 +1,50 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX CONFIG_
+$NAMESPACE isc::config
+
+% FOPEN_ERR     error opening %1: %2
+There was an error opening the given file.
+
+% JSON_PARSE    JSON parse error in %1: %2
+There was a parse error in 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.
+
+% MODULE_SPEC   module specification error in %1: %2
+The given file does not appear to be a valid specification file. Please
+verify that the filename is correct and that its contents are a valid
+BIND10 module specification.
+
+% MANAGER_MOD_SPEC    module specification not accepted by cfgmgr: %1
+The module specification file for this module was rejected by the
+configuration manager. The full error message answer from the
+configuration manager is appended to the log error. The most likely
+cause is that the module is of a different (specification file) version
+than the running configuration manager.
+
+% MANAGER_CONFIG    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
+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.
+
+% CCSESSION_MSG error in CC session message: %1
+There was a problem with an incoming message on the command and control
+channel. The message does not appear to be a valid command, and is
+missing a required element or contains an unknown data format. This
+most likely means that another BIND10 module is sending a bad message.
+The message itself is ignored by this module.
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index 0cebdbf..0d2c29b 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -24,6 +24,7 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD =  $(GTEST_LDADD)
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 run_unittests_LDADD += libfake_session.la
 run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
 
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 43663bd..3564d4b 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -264,10 +264,7 @@ TEST_F(CCSessionTest, checkCommand) {
 
     session.addMessage(el("{ \"command\": \"bad_command\" }"), "Spec29", "*");
     result = mccs.checkCommand();
-    EXPECT_EQ(1, session.getMsgQueue()->size());
-    msg = session.getFirstMessage(group, to);
-    EXPECT_EQ("{ \"result\": [ 1, \"Command part in command message missing, empty, or not a list\" ] }", msg->str());
-    EXPECT_EQ(0, result);
+    EXPECT_EQ(0, session.getMsgQueue()->size());
 
     session.addMessage(el("{ \"command\": [ \"bad_command\" ] }"),
                        "Spec29", "*");
@@ -349,6 +346,18 @@ TEST_F(CCSessionTest, checkCommand2) {
     EXPECT_EQ(2, mccs.getValue("item1")->intValue());
 }
 
+std::string remote_module_name;
+int remote_item1(0);
+ConstElementPtr remote_config;
+ModuleCCSession *remote_mccs(NULL);
+
+void remoteHandler(const std::string& module_name, ConstElementPtr config) {
+    remote_module_name = module_name;
+    remote_item1 = remote_mccs->getRemoteConfigValue("Spec2", "item1")->
+        intValue();
+    remote_config = config;
+}
+
 TEST_F(CCSessionTest, remoteConfig) {
     std::string module_name;
     int item1;
@@ -395,6 +404,91 @@ TEST_F(CCSessionTest, remoteConfig) {
     
     session.getMessages()->add(createAnswer());
     EXPECT_THROW(mccs.addRemoteConfig(ccspecfile("spec2.spec")), CCSessionError);
+
+    {
+        SCOPED_TRACE("With module name");
+        // Try adding it with downloading the spec from config manager
+        ModuleSpec spec(moduleSpecFromFile(ccspecfile("spec2.spec")));
+        session.getMessages()->add(createAnswer(0, spec.getFullSpec()));
+        session.getMessages()->add(createAnswer(0, el("{}")));
+
+        EXPECT_NO_THROW(module_name = mccs.addRemoteConfig("Spec2", NULL,
+                                                           false));
+
+        EXPECT_EQ("Spec2", module_name);
+        EXPECT_NO_THROW(item1 =
+                        mccs.getRemoteConfigValue(module_name,
+                                                  "item1")->intValue());
+        EXPECT_EQ(1, item1);
+
+        mccs.removeRemoteConfig(module_name);
+    }
+
+    {
+        // Try adding it with a handler.
+        // Pass non-default value to see the handler is called after
+        // downloading the configuration, not too soon.
+        SCOPED_TRACE("With handler");
+        session.getMessages()->add(createAnswer(0, el("{ \"item1\": 2 }")));
+        remote_mccs = &mccs;
+        module_name = mccs.addRemoteConfig(ccspecfile("spec2.spec"),
+                                           remoteHandler);
+        {
+            SCOPED_TRACE("Before update");
+            EXPECT_EQ("Spec2", module_name);
+            EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+            // Now check the parameters the remote handler stored
+            // This also checks it was called
+            EXPECT_EQ("Spec2", remote_module_name);
+            remote_module_name = "";
+            EXPECT_EQ(2, remote_item1);
+            remote_item1 = 0;
+            if (remote_config) {
+                EXPECT_EQ(2, remote_config->get("item1")->intValue());
+            } else {
+                ADD_FAILURE() << "Remote config not set";
+            }
+            remote_config.reset();
+            // Make sure normal way still works
+            item1 = mccs.getRemoteConfigValue(module_name,
+                                              "item1")->intValue();
+            EXPECT_EQ(2, item1);
+        }
+
+        {
+            SCOPED_TRACE("After update");
+            session.addMessage(el("{ \"command\": [ \"config_update\", "
+                                  "{ \"item1\": 3 } ] }"), module_name, "*");
+            mccs.checkCommand();
+            EXPECT_EQ("Spec2", remote_module_name);
+            remote_module_name = "";
+            EXPECT_EQ(3, remote_item1);
+            remote_item1 = 0;
+            if (remote_config) {
+                EXPECT_EQ(3, remote_config->get("item1")->intValue());
+            } else {
+                ADD_FAILURE() << "Remote config not set";
+            }
+            remote_config.reset();
+            // Make sure normal way still works
+            item1 = mccs.getRemoteConfigValue(module_name,
+                                              "item1")->intValue();
+            EXPECT_EQ(3, item1);
+        }
+
+        remote_mccs = NULL;
+        mccs.removeRemoteConfig(module_name);
+
+        {
+            SCOPED_TRACE("When removed");
+            // Make sure nothing is called any more
+            session.addMessage(el("{ \"command\": [ \"config_update\", "
+                                  "{ \"item1\": 4 } ] }"), module_name, "*");
+            EXPECT_EQ("", remote_module_name);
+            EXPECT_EQ(0, remote_item1);
+            EXPECT_FALSE(remote_config);
+        }
+    }
 }
 
 TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
@@ -432,4 +526,24 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
     EXPECT_EQ(0, session.getMsgQueue()->size());
 }
 
+TEST_F(CCSessionTest, initializationFail) {
+    // bad specification
+    EXPECT_THROW(ModuleCCSession(ccspecfile("spec8.spec"), session,
+                                 NULL, NULL), CCSessionInitError);
+
+    // file that does not exist
+    EXPECT_THROW(ModuleCCSession(ccspecfile("does_not_exist_spec"),
+                                 session, NULL, NULL),
+                                 CCSessionInitError);
+
+
+    session.getMessages()->add(createAnswer(1, el("\"just an error\"")));
+
+    EXPECT_FALSE(session.haveSubscription("Spec29", "*"));
+    EXPECT_THROW(ModuleCCSession(ccspecfile("spec29.spec"), session,
+                                 my_config_handler, my_command_handler),
+                                 CCSessionInitError);
+    EXPECT_TRUE(session.haveSubscription("Spec29", "*"));
+}
+
 }
diff --git a/src/lib/config/tests/run_unittests.cc b/src/lib/config/tests/run_unittests.cc
index 0908071..fab90f5 100644
--- a/src/lib/config/tests/run_unittests.cc
+++ b/src/lib/config/tests/run_unittests.cc
@@ -13,9 +13,16 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <gtest/gtest.h>
+#include <log/logger_support.h>
 
 int
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
+
+    // TODO: UNCOMMENT ON MERGE
+    // (this is the call we want in master, but branch point does not
+    // have this yet)
+    //isc::log::initLogger();
+
     return (RUN_ALL_TESTS());
 }
diff --git a/src/lib/cryptolink/crypto_hmac.cc b/src/lib/cryptolink/crypto_hmac.cc
index 9ca99a1..c2b4265 100644
--- a/src/lib/cryptolink/crypto_hmac.cc
+++ b/src/lib/cryptolink/crypto_hmac.cc
@@ -153,8 +153,17 @@ public:
         // Botan's verify_mac checks if len matches the output_length,
         // which causes it to fail for truncated signatures, so we do
         // the check ourselves
+        // SEE BELOW FOR TEMPORARY CHANGE
         try {
             Botan::SecureVector<Botan::byte> our_mac = hmac_->final();
+            if (len < getOutputLength()) {
+                // Currently we don't support truncated signature.  To avoid
+                // validating too short signature accidently, we enforce the
+                // standard signature size for the moment.
+                // Once we support truncation correctly, this if-clause should
+                // (and the capitalized comment above) be removed.
+                return (false);
+            }
             if (len == 0 || len > getOutputLength()) {
                 len = getOutputLength();
             }
diff --git a/src/lib/cryptolink/tests/crypto_unittests.cc b/src/lib/cryptolink/tests/crypto_unittests.cc
index b59a0b2..e8be429 100644
--- a/src/lib/cryptolink/tests/crypto_unittests.cc
+++ b/src/lib/cryptolink/tests/crypto_unittests.cc
@@ -233,18 +233,6 @@ TEST(CryptoLinkTest, HMAC_MD5_RFC2202_SIGN) {
                                        0x79 };
     doHMACTest(data4, secret4, 25, MD5, hmac_expected4, 16);
 
-    const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
-                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
-                                0x0c, 0x0c, 0x0c, 0x0c };
-    const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34,
-                                       0x2e, 0xdc, 0x00, 0xf9, 0xba,
-                                       0xb9, 0x95, 0x69, 0x0e, 0xfd,
-                                       0x4c };
-    doHMACTest("Test With Truncation", secret5, 16, MD5,
-               hmac_expected5, 16);
-    doHMACTest("Test With Truncation", secret5, 16, MD5,
-               hmac_expected5, 12);
-
     const uint8_t hmac_expected6[] = { 0x6b, 0x1a, 0xb7, 0xfe, 0x4b,
                                        0xd7, 0xbf, 0x8f, 0x0b, 0x62,
                                        0xe6, 0xce, 0x61, 0xb9, 0xd0,
@@ -261,6 +249,21 @@ TEST(CryptoLinkTest, HMAC_MD5_RFC2202_SIGN) {
                std::string(80, 0xaa).c_str(), 80, MD5, hmac_expected7, 16);
 }
 
+// Temporarily disabled
+TEST(CryptoLinkTest, DISABLED_HMAC_MD5_RFC2202_SIGN_TRUNCATED) {
+    const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c, 0x0c, 0x0c };
+    const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34,
+                                       0x2e, 0xdc, 0x00, 0xf9, 0xba,
+                                       0xb9, 0x95, 0x69, 0x0e, 0xfd,
+                                       0x4c };
+    doHMACTest("Test With Truncation", secret5, 16, MD5,
+               hmac_expected5, 16);
+    doHMACTest("Test With Truncation", secret5, 16, MD5,
+               hmac_expected5, 12);
+}
+
 //
 // Test values taken from RFC 2202
 //
@@ -302,19 +305,6 @@ TEST(CryptoLinkTest, HMAC_SHA1_RFC2202_SIGN) {
                                        0x6c, 0x2d, 0x72, 0x35, 0xda };
     doHMACTest(std::string(50, 0xcd), secret4, 25, SHA1, hmac_expected4, 20);
 
-    const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
-                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
-                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
-                                0x0c, 0x0c };
-    const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b,
-                                       0x55, 0xe0, 0x7f, 0xe7, 0xf2,
-                                       0x7b, 0xe1, 0xd5, 0x8b, 0xb9,
-                                       0x32, 0x4a, 0x9a, 0x5a, 0x04 };
-    doHMACTest("Test With Truncation", secret5, 20, SHA1,
-               hmac_expected5, 20);
-    doHMACTest("Test With Truncation", secret5, 20, SHA1,
-               hmac_expected5, 12);
-
     const uint8_t hmac_expected6[] = { 0xaa, 0x4a, 0xe5, 0xe1, 0x52,
                                        0x72, 0xd0, 0x0e, 0x95, 0x70,
                                        0x56, 0x37, 0xce, 0x8a, 0x3b,
@@ -331,6 +321,22 @@ TEST(CryptoLinkTest, HMAC_SHA1_RFC2202_SIGN) {
                std::string(80, 0xaa).c_str(), 80, SHA1, hmac_expected7, 20);
 }
 
+// Temporarily disabled
+TEST(CryptoLinkTest, DISABLED_HMAC_SHA1_RFC2202_SIGN_TRUNCATED) {
+    const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c };
+    const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b,
+                                       0x55, 0xe0, 0x7f, 0xe7, 0xf2,
+                                       0x7b, 0xe1, 0xd5, 0x8b, 0xb9,
+                                       0x32, 0x4a, 0x9a, 0x5a, 0x04 };
+    doHMACTest("Test With Truncation", secret5, 20, SHA1,
+               hmac_expected5, 20);
+    doHMACTest("Test With Truncation", secret5, 20, SHA1,
+               hmac_expected5, 12);
+}
+
 //
 // Test values taken from RFC 4231
 //
@@ -433,10 +439,15 @@ TEST(CryptoLinkTest, HMAC_SHA256_RFC4231_SIGN) {
                                        0xe5, 0x78, 0xf8, 0x07, 0x7a,
                                        0x2e, 0x3f, 0xf4, 0x67, 0x29,
                                        0x66, 0x5b };
+<<<<<<< HEAD
     const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73,
                                        0x10, 0x0e, 0xe0, 0x6e, 0x0c,
                                        0x79, 0x6c, 0x29, 0x55, 0x55,
                                        0x2b };
+=======
+    doHMACTest(std::string(50, 0xcd), secret4, 25, SHA256, hmac_expected4, 32);
+
+>>>>>>> master
     const uint8_t hmac_expected6[] = { 0x60, 0xe4, 0x31, 0x59, 0x1e,
                                        0xe0, 0xb6, 0x7f, 0x0d, 0x8a,
                                        0x26, 0xaa, 0xcb, 0xf5, 0xb7,
@@ -613,6 +624,19 @@ TEST(CryptoLinkTest, HMAC_SHA512_RFC4231_SIGN) {
     RUN_TEST_CASES_FOR_ALG(SHA512);
 }
 
+TEST(CryptoLinkTest, DISABLED_HMAC_SHA256_RFC2202_SIGN_TRUNCATED) {
+    const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+                                0x0c, 0x0c };
+    const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73,
+                                       0x10, 0x0e, 0xe0, 0x6e, 0x0c,
+                                       0x79, 0x6c, 0x29, 0x55, 0x55,
+                                       0x2b };
+    doHMACTest("Test With Truncation", secret5, 20, SHA256,
+               hmac_expected5, 16);
+}
+
 namespace {
     size_t
     sigVectorLength(HashAlgorithm alg, size_t len) {
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index adb1d41..e028186 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -7,7 +7,7 @@ AM_CPPFLAGS += $(SQLITE_CFLAGS)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
-CLEANFILES = *.gcno *.gcda
+CLEANFILES = *.gcno *.gcda messagedef.h messagedef.cc
 
 lib_LTLIBRARIES = libdatasrc.la
 libdatasrc_la_SOURCES = data_source.h data_source.cc
@@ -20,3 +20,16 @@ libdatasrc_la_SOURCES += zonetable.h zonetable.cc
 libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
 libdatasrc_la_SOURCES += zone.h
 libdatasrc_la_SOURCES += result.h
+libdatasrc_la_SOURCES += logger.h logger.cc
+nodist_libdatasrc_la_SOURCES = messagedef.h messagedef.cc
+
+libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
+libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+libdatasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
+
+BUILT_SOURCES = messagedef.h messagedef.cc
+messagedef.h messagedef.cc: Makefile messagedef.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/messagedef.mes
+
+EXTRA_DIST = messagedef.mes
diff --git a/src/lib/datasrc/cache.cc b/src/lib/datasrc/cache.cc
index 6fff754..8e9487d 100644
--- a/src/lib/datasrc/cache.cc
+++ b/src/lib/datasrc/cache.cc
@@ -24,6 +24,7 @@
 #include <list>
 
 #include <datasrc/cache.h>
+#include <datasrc/logger.h>
 
 using namespace std;
 using namespace isc::dns;
@@ -204,16 +205,21 @@ public:
 // HotCacheImpl constructor
 HotCacheImpl::HotCacheImpl(int slots, bool enabled) :
     enabled_(enabled), slots_(slots), count_(0)
-{}
+{
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_CACHE_CREATE);
+}
 
 // Insert a cache node into the cache
 inline void
 HotCacheImpl::insert(const CacheNodePtr node) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_INSERT).
+        arg(node->getRRset()->getName());
     std::map<Question, CacheNodePtr>::const_iterator iter;
     iter = map_.find(node->question);
     if (iter != map_.end()) {
         CacheNodePtr old = iter->second;
         if (old && old->isValid()) {
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_OLD_FOUND);
             remove(old);
         }
     }
@@ -225,6 +231,7 @@ HotCacheImpl::insert(const CacheNodePtr node) {
     ++count_;
 
     if (slots_ != 0 && count_ > slots_) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_FULL);
         remove(lru_.back());
     }
 }
@@ -245,6 +252,8 @@ HotCacheImpl::promote(CacheNodePtr node) {
 // Remove a node from the LRU list and the map
 void
 HotCacheImpl::remove(ConstCacheNodePtr node) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_REMOVE).
+        arg(node->getRRset()->getName());
     lru_.erase(node->lru_entry_);
     map_.erase(node->question);
     --count_;
@@ -257,6 +266,7 @@ HotCache::HotCache(const int slots) {
 
 // HotCache destructor
 HotCache::~HotCache() {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_CACHE_DESTROY);
     delete impl_;
 }
 
@@ -303,18 +313,21 @@ HotCache::retrieve(const Name& n, const RRClass& c, const RRType& t,
     std::map<Question, CacheNodePtr>::const_iterator iter;
     iter = impl_->map_.find(Question(n, c, t));
     if (iter == impl_->map_.end()) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_NOT_FOUND).arg(n);
         return (false);
     }
 
     CacheNodePtr node = iter->second;
 
     if (node->isValid()) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_FOUND).arg(n);
         impl_->promote(node);
         rrset = node->getRRset();
         flags = node->getFlags();
         return (true);
     }
 
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_CACHE_EXPIRED).arg(n);
     impl_->remove(node);
     return (false);
 }
@@ -328,6 +341,9 @@ HotCache::setSlots(const int slots) {
         return;
     }
 
+    logger.info(DATASRC_CACHE_SLOTS).arg(slots).arg(max(0, impl_->count_ -
+                                                        slots));
+
     while (impl_->slots_ != 0 && impl_->count_ > impl_->slots_) {
         impl_->remove(impl_->lru_.back());
     }
@@ -343,6 +359,11 @@ HotCache::getSlots() const {
 void
 HotCache::setEnabled(const bool e) {
     impl_->enabled_ = e;
+    if (e) {
+        logger.info(DATASRC_CACHE_ENABLE);
+    } else {
+        logger.info(DATASRC_CACHE_DISABLE);
+    }
 }
 
 /// Indicate whether the cache is enabled
diff --git a/src/lib/datasrc/data_source.cc b/src/lib/datasrc/data_source.cc
index 548a811..4e1fcde 100644
--- a/src/lib/datasrc/data_source.cc
+++ b/src/lib/datasrc/data_source.cc
@@ -25,6 +25,7 @@
 #include <datasrc/cache.h>
 #include <datasrc/data_source.h>
 #include <datasrc/query.h>
+#include <datasrc/logger.h>
 
 #include <util/encode/base32hex.h>
 #include <util/hash/sha1.h>
@@ -83,7 +84,7 @@ class ZoneInfo {
 public:
     ZoneInfo(DataSrc* ts,
              const isc::dns::Name& n,
-             const isc::dns::RRClass& c, 
+             const isc::dns::RRClass& c,
              const isc::dns::RRType& t = isc::dns::RRType::ANY()) :
         top_source_(ts),
         dsm_(((t == RRType::DS() && n.getLabelCount() != 1)
@@ -123,6 +124,8 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
         const Rdata& rd(it->getCurrent());
         if (rrset->getType() == RRType::NS()) {
             const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_GET_NS_ADDITIONAL).
+                arg(ns.getNSName()).arg(rrset->getName());
             q.tasks().push(QueryTaskPtr(
                                new QueryTask(q, ns.getNSName(),
                                              Message::SECTION_ADDITIONAL,
@@ -130,6 +133,8 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
                                              QueryTask::GETADDITIONAL)));
         } else if (rrset->getType() == RRType::MX()) {
             const generic::MX& mx = dynamic_cast<const generic::MX&>(rd);
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_GET_MX_ADDITIONAL).
+                arg(mx.getMXName()).arg(rrset->getName());
             q.tasks().push(QueryTaskPtr(
                                new QueryTask(q, mx.getMXName(),
                                              Message::SECTION_ADDITIONAL,
@@ -143,11 +148,14 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
 // understand DNAME
 void
 synthesizeCname(QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_SYNTH_CNAME).
+        arg(rrset->getName());
     RdataIteratorPtr it = rrset->getRdataIterator();
 
     // More than one DNAME RR in the RRset is illegal, so we only have
     // to process the first one.
     if (it->isLast()) {
+        logger.error(DATASRC_QUERY_EMPTY_DNAME).arg(rrset->getName());
         return;
     }
 
@@ -171,16 +179,20 @@ synthesizeCname(QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
 // to by a CNAME record
 void
 chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_FOLLOW_CNAME).
+        arg(rrset->getName());
     RdataIteratorPtr it = rrset->getRdataIterator();
 
     // More than one CNAME RR in the RRset is illegal, so we only have
     // to process the first one.
     if (it->isLast()) {
+        logger.error(DATASRC_QUERY_EMPTY_CNAME).arg(rrset->getName());
         return;
     }
 
     // Stop chasing CNAMES after 16 lookups, to prevent loops
     if (q.tooMany()) {
+        logger.error(DATASRC_QUERY_TOO_MANY_CNAMES).arg(rrset->getName());
         return;
     }
 
@@ -194,6 +206,8 @@ chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
 // Check the cache for data which can answer the current query task.
 bool
 checkCache(QueryTask& task, RRsetList& target) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_CHECK_CACHE).
+        arg(task.qname).arg(task.qtype);
     HotCache& cache = task.q.getCache();
     RRsetList rrsets;
     RRsetPtr rrset;
@@ -206,6 +220,9 @@ checkCache(QueryTask& task, RRsetList& target) {
         // ANY queries must be handled by the low-level data source,
         // or the results won't be guaranteed to be complete
         if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) {
+            LOG_DEBUG(logger, DBG_TRACE_DATA,
+                      DATASRC_QUERY_NO_CACHE_ANY_SIMPLE).arg(task.qname).
+                arg(task.qtype).arg(task.qclass);
             break;
         }
 
@@ -235,6 +252,8 @@ checkCache(QueryTask& task, RRsetList& target) {
 
     case QueryTask::AUTH_QUERY:         // Find exact RRset or CNAME
         if (task.qtype == RRType::ANY() || task.qclass == RRClass::ANY()) {
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_NO_CACHE_ANY_AUTH).
+                arg(task.qname).arg(task.qtype).arg(task.qclass);
             break;
         }
 
@@ -353,6 +372,8 @@ DataSrc::Result
 doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
     HotCache& cache = task.q.getCache();
     RRsetPtr rrset;
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_DO_QUERY).arg(task.qname).
+        arg(task.qtype);
 
     // First off, make sure at least we have a matching zone in some data
     // source.  We must do this before checking the cache, because it can
@@ -363,11 +384,14 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
     const Name* const zonename = zoneinfo.getEnclosingZone();
     if (ds == NULL) {
         task.flags |= DataSrc::NO_SUCH_ZONE;
+        logger.info(DATASRC_QUERY_NO_ZONE).arg(task.qname).arg(task.qclass);
         return (DataSrc::SUCCESS);
     }
 
     // Then check the cache for matching data
     if (checkCache(task, target)) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_CACHED).
+            arg(task.qname).arg(task.qtype);
         return (DataSrc::SUCCESS);
     }
 
@@ -378,10 +402,13 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
     DataSrc::Result result;
     switch (task.op) {
     case QueryTask::SIMPLE_QUERY:
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_IS_SIMPLE).
+            arg(task.qname).arg(task.qtype);
         result = ds->findExactRRset(task.qname, task.qclass, task.qtype,
                                     target, task.flags, zonename);
 
         if (result != DataSrc::SUCCESS) {
+            logger.error(DATASRC_QUERY_SIMPLE_FAIL).arg(result);
             return (result);
         }
 
@@ -403,10 +430,13 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
         return (result);
 
     case QueryTask::AUTH_QUERY:
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_IS_AUTH).
+            arg(task.qname).arg(task.qtype);
         result = ds->findRRset(task.qname, task.qclass, task.qtype,
                                target, task.flags, zonename);
 
         if (result != DataSrc::SUCCESS) {
+            logger.error(DATASRC_QUERY_AUTH_FAIL).arg(result);
             return (result);
         }
 
@@ -439,10 +469,16 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
 
     case QueryTask::GLUE_QUERY:
     case QueryTask::NOGLUE_QUERY:
+        LOG_DEBUG(logger, DBG_TRACE_DATA, task.op == QueryTask::GLUE_QUERY ?
+                     DATASRC_QUERY_IS_GLUE : DATASRC_QUERY_IS_NOGLUE).
+            arg(task.qname).arg(task.qtype);
         result = ds->findAddrs(task.qname, task.qclass, target,
                                task.flags, zonename);
 
         if (result != DataSrc::SUCCESS) {
+            logger.error(task.op == QueryTask::GLUE_QUERY ?
+                         DATASRC_QUERY_GLUE_FAIL : DATASRC_QUERY_NOGLUE_FAIL).
+                arg(result);
             return (result);
         }
 
@@ -468,10 +504,13 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
         return (result);
 
     case QueryTask::REF_QUERY:
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_IS_REF).
+            arg(task.qname).arg(task.qtype);
         result = ds->findReferral(task.qname, task.qclass, target,
                                  task.flags, zonename);
 
         if (result != DataSrc::SUCCESS) {
+            logger.error(DATASRC_QUERY_REF_FAIL).arg(result);
             return (result);
         }
 
@@ -505,6 +544,7 @@ doQueryTask(QueryTask& task, ZoneInfo& zoneinfo, RRsetList& target) {
     }
 
     // Not reached
+    logger.error(DATASRC_QUERY_INVALID_OP);
     return (DataSrc::ERROR);
 }
 
@@ -516,6 +556,8 @@ inline void
 addToMessage(Query& q, const Message::Section sect, RRsetPtr rrset,
              bool no_dnssec = false)
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_RRSET).
+        arg(rrset->getName()).arg(rrset->getType());
     Message& m = q.message();
     if (no_dnssec) {
         if (rrset->getType() == RRType::RRSIG() ||
@@ -534,6 +576,7 @@ addToMessage(Query& q, const Message::Section sect, RRsetPtr rrset,
 // Copy referral information into the authority section of a message
 inline void
 copyAuth(Query& q, RRsetList& auth) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_COPY_AUTH);
     BOOST_FOREACH(RRsetPtr rrset, auth) {
         if (rrset->getType() == RRType::DNAME()) {
             continue;
@@ -571,6 +614,9 @@ refQuery(const Query& q, const Name& name, ZoneInfo& zoneinfo,
 // they'll be handled in a normal lookup in the zone.
 inline bool
 hasDelegation(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_DELEGATION).
+        arg(task->qname);
+
     const Name* const zonename = zoneinfo.getEnclosingZone();
     if (zonename == NULL) {
         if (task->state == QueryTask::GETANSWER) {
@@ -636,6 +682,7 @@ addSOA(Query& q, ZoneInfo& zoneinfo) {
     RRsetList soa;
 
     const Name* const zonename = zoneinfo.getEnclosingZone();
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_SOA).arg(*zonename);
     QueryTask newtask(q, *zonename, RRType::SOA(), QueryTask::SIMPLE_QUERY);
     RETERR(doQueryTask(newtask, zoneinfo, soa));
     if (newtask.flags != 0) {
@@ -649,6 +696,7 @@ addSOA(Query& q, ZoneInfo& zoneinfo) {
 
 inline DataSrc::Result
 addNSEC(Query& q, const Name& name, ZoneInfo& zoneinfo) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_NSEC).arg(name);
     RRsetList nsec;
 
     QueryTask newtask(q, name, RRType::NSEC(), QueryTask::SIMPLE_QUERY); 
@@ -665,9 +713,11 @@ inline DataSrc::Result
 getNsec3(Query& q, ZoneInfo& zoneinfo, string& hash, RRsetPtr& target) {
     const DataSrc* ds = zoneinfo.getDataSource();
     const Name* const zonename = zoneinfo.getEnclosingZone();
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_ADD_NSEC3).arg(*zonename);
 
     if (ds == NULL) {
         q.message().setRcode(Rcode::SERVFAIL());
+        logger.error(DATASRC_QUERY_NO_DS_NSEC3).arg(*zonename);
         return (DataSrc::ERROR);
     }
 
@@ -770,6 +820,7 @@ proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
             const DataSrc* ds = zoneinfo.getDataSource();
             if (ds == NULL) {
                 m.setRcode(Rcode::SERVFAIL());
+                logger.error(DATASRC_QUERY_NO_DS_NSEC).arg(*zonename);
                 return (DataSrc::ERROR);
             }
             ds->findPreviousName(task->qname, nsecname, zonename);
@@ -798,6 +849,7 @@ proveNX(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, const bool wildcard) {
 // Attempt a wildcard lookup
 inline DataSrc::Result
 tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_QUERY_WILDCARD).arg(task->qname);
     Message& m = q.message();
     DataSrc::Result result;
     found = false;
@@ -851,6 +903,8 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
             result = proveNX(q, task, zoneinfo, true);
             if (result != DataSrc::SUCCESS) {
                 m.setRcode(Rcode::SERVFAIL());
+                logger.error(DATASRC_QUERY_WILDCARD_PROVENX_FAIL).
+                    arg(task->qname).arg(result);
                 return (DataSrc::ERROR);
             }
         }
@@ -873,6 +927,8 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
 
             RRsetList auth;
             if (!refQuery(q, *zonename, zoneinfo, auth)) {
+                logger.error(DATASRC_QUERY_WILDCARD_REFERRAL).arg(task->qname).
+                    arg(result);
                 return (DataSrc::ERROR);
             }
 
@@ -888,6 +944,8 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
 // 
 void
 DataSrc::doQuery(Query& q) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_QUERY_PROCESS).arg(q.qname()).
+        arg(q.qclass());
     Message& m = q.message();
     vector<RRsetPtr> additional;
 
@@ -905,6 +963,7 @@ DataSrc::doQuery(Query& q) {
         // Can't query directly for RRSIG.
         if (task->qtype == RRType::RRSIG()) {
             m.setRcode(Rcode::REFUSED());
+            logger.warn(DATASRC_QUERY_RRSIG).arg(task->qname);
             return;
         }
 
@@ -912,6 +971,7 @@ DataSrc::doQuery(Query& q) {
         if (task->op == QueryTask::SIMPLE_QUERY ||
             task->op == QueryTask::REF_QUERY) {
             m.setRcode(Rcode::SERVFAIL());
+            logger.error(DATASRC_QUERY_MISPLACED_TASK);
             return;
         }
 
@@ -931,6 +991,7 @@ DataSrc::doQuery(Query& q) {
         result = doQueryTask(*task, zoneinfo, data);
         if (result != SUCCESS) {
             m.setRcode(Rcode::SERVFAIL());
+            logger.error(DATASRC_QUERY_TASK_FAIL).arg(result);
             return;
         }
 
@@ -940,6 +1001,7 @@ DataSrc::doQuery(Query& q) {
         if (task->flags == NO_SUCH_ZONE) {
             if (task->state == QueryTask::GETANSWER) {
                 m.setRcode(Rcode::REFUSED());
+                // No need to log it here, it was already logged in doQueryTask
                 return;
             }
             continue;
@@ -981,6 +1043,7 @@ DataSrc::doQuery(Query& q) {
                     RRsetList auth;
                     if (!refQuery(q, Name(*zonename), zoneinfo, auth) ||
                         !findRRsetFromList(auth, RRType::NS())) {
+                        logger.error(DATASRC_QUERY_MISSING_NS).arg(*zonename);
                         isc_throw(DataSourceError,
                                   "NS RR not found in " << *zonename << "/" <<
                                   q.qclass());
@@ -1005,10 +1068,12 @@ DataSrc::doQuery(Query& q) {
                 continue;
 
             default:
+                logger.error(DATASRC_UNEXPECTED_QUERY_STATE);
                 isc_throw (Unexpected, "unexpected query state");
             }
         } else if (result == ERROR || result == NOT_IMPLEMENTED) {
             m.setRcode(Rcode::SERVFAIL());
+            logger.error(DATASRC_QUERY_FAIL);
             return;
         } else if ((task->flags & CNAME_FOUND) != 0) {
             // The qname node contains a CNAME.  Add a new task to the
@@ -1026,6 +1091,7 @@ DataSrc::doQuery(Query& q) {
                 m.setHeaderFlag(Message::HEADERFLAG_AA, false);
                 if (!refQuery(q, task->qname, zoneinfo, auth)) {
                     m.setRcode(Rcode::SERVFAIL());
+                    logger.error(DATASRC_QUERY_BAD_REFERRAL).arg(task->qname);
                     return;
                 }
                 BOOST_FOREACH (RRsetPtr rrset, auth) {
@@ -1057,6 +1123,7 @@ DataSrc::doQuery(Query& q) {
             result = tryWildcard(q, task, zoneinfo, wildcard_found);
             if (result != SUCCESS) {
                 m.setRcode(Rcode::SERVFAIL());
+                logger.error(DATASRC_QUERY_WILDCARD_FAIL).arg(task->qname);
                 return;
             }
 
@@ -1078,6 +1145,7 @@ DataSrc::doQuery(Query& q) {
 
                 result = addSOA(q, zoneinfo);
                 if (result != SUCCESS) {
+                    logger.error(DATASRC_QUERY_MISSING_SOA).arg(*zonename);
                     isc_throw(DataSourceError,
                               "SOA RR not found in " << *zonename <<
                               "/" << q.qclass());
@@ -1094,6 +1162,7 @@ DataSrc::doQuery(Query& q) {
                 result = proveNX(q, task, zoneinfo, false);
                 if (result != DataSrc::SUCCESS) {
                     m.setRcode(Rcode::SERVFAIL());
+                    logger.error(DATASRC_QUERY_PROVENX_FAIL).arg(task->qname);
                     return;
                 }
             }
@@ -1102,6 +1171,7 @@ DataSrc::doQuery(Query& q) {
         } else {
             // Should never be reached!
             m.setRcode(Rcode::SERVFAIL());
+            logger.error(DATASRC_QUERY_UNKNOWN_RESULT);
             return;
         }
     }
@@ -1197,7 +1267,10 @@ DataSrc::findReferral(const Name& qname, const RRClass& qclass,
 
 void
 MetaDataSrc::addDataSrc(ConstDataSrcPtr data_src) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_META_ADD);
     if (getClass() != RRClass::ANY() && data_src->getClass() != getClass()) {
+        logger.error(DATASRC_META_ADD_CLASS_MISMATCH).
+            arg(data_src->getClass()).arg(getClass());
         isc_throw(Unexpected, "class mismatch");
     }
 
@@ -1206,6 +1279,7 @@ MetaDataSrc::addDataSrc(ConstDataSrcPtr data_src) {
 
 void
 MetaDataSrc::removeDataSrc(ConstDataSrcPtr data_src) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_META_REMOVE);
     std::vector<ConstDataSrcPtr>::iterator it, itr;
     for (it = data_sources.begin(); it != data_sources.end(); ++it) {
         if (*it == data_src) {
diff --git a/src/lib/datasrc/logger.cc b/src/lib/datasrc/logger.cc
new file mode 100644
index 0000000..846e43b
--- /dev/null
+++ b/src/lib/datasrc/logger.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/logger.h>
+
+namespace isc {
+namespace datasrc {
+
+isc::log::Logger logger("datasrc");
+
+}
+}
diff --git a/src/lib/datasrc/logger.h b/src/lib/datasrc/logger.h
new file mode 100644
index 0000000..7c2828d
--- /dev/null
+++ b/src/lib/datasrc/logger.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DATASRC_LOGGER_H
+#define __DATASRC_LOGGER_H
+
+#include <log/macros.h>
+#include <datasrc/messagedef.h>
+
+/// \file logger.h
+/// \brief Data Source library global logger
+///
+/// This holds the logger for the data source library. It is a private header
+/// and should not be included in any publicly used header, only in local
+/// cc files.
+
+namespace isc {
+namespace datasrc {
+
+/// \brief The logger for this library
+extern isc::log::Logger logger;
+
+enum {
+    /// \brief Trace basic operations
+    DBG_TRACE_BASIC = 10,
+    /// \brief Trace data changes and lookups as well
+    DBG_TRACE_DATA = 20,
+    /// \brief Detailed even about how the lookups happen
+    DBG_TRACE_DETAILED = 50
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 5230ced..3c57d1b 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -24,6 +24,7 @@
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/rbtree.h>
+#include <datasrc/logger.h>
 
 using namespace std;
 using namespace isc::dns;
@@ -94,6 +95,8 @@ struct MemoryZone::MemoryZoneImpl {
              l > origin_labels;
              --l, wname = wname.split(1)) {
             if (wname.isWildcard()) {
+                LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_WILDCARD).
+                    arg(name);
                 // Ensure a separate level exists for the "wildcarding" name,
                 // and mark the node as "wild".
                 DomainNode* node;
@@ -130,10 +133,13 @@ struct MemoryZone::MemoryZoneImpl {
             // (depending on how we support DNSSEC).  We should revisit it
             // at that point.
             if (!domain->empty()) {
+                LOG_ERROR(logger, DATASRC_MEM_CNAME_TO_NONEMPTY).
+                    arg(rrset->getName());
                 isc_throw(AddError, "CNAME can't be added with other data for "
                           << 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());
         }
@@ -151,6 +157,7 @@ struct MemoryZone::MemoryZoneImpl {
             (rrset->getType() == RRType::NS() &&
             domain->find(RRType::DNAME()) != domain->end())))
         {
+            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());
         }
@@ -172,6 +179,8 @@ struct MemoryZone::MemoryZoneImpl {
             // XXX: this is not only for CNAME or DNAME. We should generalize
             // this code for all other "singleton RR types" (such as SOA) in a
             // separate task.
+            LOG_ERROR(logger, DATASRC_MEM_SINGLETON).arg(rrset->getName()).
+                arg(rrset->getType());
             isc_throw(AddError, "multiple RRs of singleton type for "
                       << rrset->getName());
         }
@@ -180,6 +189,8 @@ struct MemoryZone::MemoryZoneImpl {
         if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
             compare.getRelation() != NameComparisonResult::EQUAL)
         {
+            LOG_ERROR(logger, DATASRC_MEM_OUT_OF_ZONE).arg(rrset->getName()).
+                arg(origin_);
             isc_throw(OutOfZone, "The name " << rrset->getName() <<
                 " is not contained in zone " << origin_);
         }
@@ -194,10 +205,14 @@ struct MemoryZone::MemoryZoneImpl {
         // behavior.
         if (rrset->getName().isWildcard()) {
             if (rrset->getType() == RRType::NS()) {
+                LOG_ERROR(logger, DATASRC_MEM_WILDCARD_NS).
+                    arg(rrset->getName());
                 isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
                           rrset->getName());
             }
             if (rrset->getType() == RRType::DNAME()) {
+                LOG_ERROR(logger, DATASRC_MEM_WILDCARD_DNAME).
+                    arg(rrset->getName());
                 isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
                           rrset->getName());
             }
@@ -210,6 +225,8 @@ struct MemoryZone::MemoryZoneImpl {
      */
     // Implementation of MemoryZone::add
     result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
+            arg(rrset->getName()).arg(rrset->getType()).arg(origin_);
         // Sanitize input
         addValidation(rrset);
 
@@ -271,6 +288,8 @@ struct MemoryZone::MemoryZoneImpl {
     void addFromLoad(const ConstRRsetPtr& set, DomainTree* domains) {
             switch (add(set, domains)) {
                 case result::EXIST:
+                    LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
+                        arg(set->getName()).arg(set->getType());
                     isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
                         set->toText());
                 case result::SUCCESS:
@@ -307,6 +326,8 @@ struct MemoryZone::MemoryZoneImpl {
         const Domain::const_iterator foundDNAME(node.getData()->find(
             RRType::DNAME()));
         if (foundDNAME != node.getData()->end()) {
+            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+                      DATASRC_MEM_DNAME_ENCOUNTERED);
             state->dname_node_ = &node;
             state->rrset_ = foundDNAME->second;
             // No more processing below the DNAME (RFC 2672, section 3
@@ -328,6 +349,8 @@ struct MemoryZone::MemoryZoneImpl {
                 return (false);
             }
 
+            LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
+
             // BIND 9 checks if this node is not the origin.  That's probably
             // because it can support multiple versions for dynamic updates
             // and IXFR, and it's possible that the callback is called at
@@ -363,6 +386,8 @@ struct MemoryZone::MemoryZoneImpl {
         rrset, bool rename)
     {
         if (rename) {
+            LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_RENAME).
+                arg(rrset->getName()).arg(name);
             /*
              * We lose a signature here. But it would be wrong anyway, because
              * the name changed. This might turn out to be unimportant in
@@ -385,6 +410,8 @@ struct MemoryZone::MemoryZoneImpl {
     FindResult find(const Name& name, RRType type,
                     RRsetList* target, const FindOptions options) const
     {
+        LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FIND).arg(name).
+            arg(type);
         // Get the node
         DomainNode* node(NULL);
         FindState state(options);
@@ -411,12 +438,16 @@ struct MemoryZone::MemoryZoneImpl {
                  * is NULL.
                  */
                 if (state.dname_node_ != NULL) {
+                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
+                        arg(state.rrset_->getName());
                     // We were traversing a DNAME node (and wanted to go
                     // lower below it), so return the DNAME
                     return (FindResult(DNAME, prepareRRset(name, state.rrset_,
                         rename)));
                 }
                 if (state.zonecut_node_ != NULL) {
+                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
+                        arg(state.rrset_->getName());
                     return (FindResult(DELEGATION, prepareRRset(name,
                         state.rrset_, rename)));
                 }
@@ -426,6 +457,8 @@ struct MemoryZone::MemoryZoneImpl {
                 // the zone but is empty.  Treat it as NXRRSET.
                 if (node_path.getLastComparisonResult().getRelation() ==
                     NameComparisonResult::SUPERDOMAIN) {
+                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).
+                        arg(node_path.getAbsoluteName()).arg(name);
                     return (FindResult(NXRRSET, ConstRRsetPtr()));
                 }
 
@@ -463,6 +496,8 @@ struct MemoryZone::MemoryZoneImpl {
                     if (node_path.getLastComparisonResult().getRelation() ==
                         NameComparisonResult::COMMONANCESTOR && node_path.
                         getLastComparisonResult().getCommonLabels() > 1) {
+                        LOG_DEBUG(logger, DBG_TRACE_DATA,
+                                     DATASRC_MEM_WILDCARD_CANCEL).arg(name);
                         return (FindResult(NXDOMAIN, ConstRRsetPtr()));
                     }
                     Name wildcard(Name("*").concatenate(
@@ -485,6 +520,8 @@ struct MemoryZone::MemoryZoneImpl {
 
                 // fall through
             case DomainTree::NOTFOUND:
+                LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOTFOUND).
+                    arg(name);
                 return (FindResult(NXDOMAIN, ConstRRsetPtr()));
             case DomainTree::EXACTMATCH: // This one is OK, handle it
                 break;
@@ -496,6 +533,8 @@ struct MemoryZone::MemoryZoneImpl {
         // If there is an exact match but the node is empty, it's equivalent
         // to NXRRSET.
         if (node->isEmpty()) {
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
+                arg(name);
             return (FindResult(NXRRSET, ConstRRsetPtr()));
         }
 
@@ -506,6 +545,8 @@ struct MemoryZone::MemoryZoneImpl {
         if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_) {
             found = node->getData()->find(RRType::NS());
             if (found != node->getData()->end()) {
+                LOG_DEBUG(logger, DBG_TRACE_DATA,
+                          DATASRC_MEM_EXACT_DELEGATION).arg(name);
                 return (FindResult(DELEGATION, prepareRRset(name,
                     found->second, rename)));
             }
@@ -521,23 +562,30 @@ struct MemoryZone::MemoryZoneImpl {
                     boost::const_pointer_cast<RRset>(prepareRRset(name,
                     found->second, rename)));
             }
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
+                arg(name);
             return (FindResult(SUCCESS, ConstRRsetPtr()));
         }
 
         found = node->getData()->find(type);
         if (found != node->getData()->end()) {
             // Good, it is here
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
+                arg(type);
             return (FindResult(SUCCESS, prepareRRset(name, found->second,
                 rename)));
         } else {
             // Next, try CNAME.
             found = node->getData()->find(RRType::CNAME());
             if (found != node->getData()->end()) {
+                LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
                 return (FindResult(CNAME, prepareRRset(name, found->second,
                     rename)));
             }
         }
         // No exact match or CNAME.  Return NXRRSET.
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NXRRSET).arg(type).
+            arg(name);
         return (FindResult(NXRRSET, ConstRRsetPtr()));
     }
 };
@@ -545,9 +593,13 @@ struct MemoryZone::MemoryZoneImpl {
 MemoryZone::MemoryZone(const RRClass& zone_class, const Name& origin) :
     impl_(new MemoryZoneImpl(zone_class, origin))
 {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_CREATE).arg(origin).
+        arg(zone_class);
 }
 
 MemoryZone::~MemoryZone() {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_DESTROY).arg(getOrigin()).
+        arg(getClass());
     delete impl_;
 }
 
@@ -576,6 +628,8 @@ MemoryZone::add(const ConstRRsetPtr& rrset) {
 
 void
 MemoryZone::load(const string& filename) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
+        arg(filename);
     // Load it into a temporary tree
     MemoryZoneImpl::DomainTree tmp;
     masterLoad(filename.c_str(), getOrigin(), getClass(),
@@ -588,6 +642,8 @@ MemoryZone::load(const string& filename) {
 
 void
 MemoryZone::swap(MemoryZone& zone) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_SWAP).arg(getOrigin()).
+        arg(zone.getOrigin());
     std::swap(impl_, zone.impl_);
 }
 
@@ -628,6 +684,9 @@ MemoryDataSrc::addZone(ZonePtr zone) {
                   "Null pointer is passed to MemoryDataSrc::addZone()");
     }
 
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_ADD_ZONE).
+        arg(zone->getOrigin()).arg(zone->getClass().toText());
+
     const result::Result result = impl_->zone_table.addZone(zone);
     if (result == result::SUCCESS) {
         ++impl_->zone_count;
@@ -637,6 +696,7 @@ MemoryDataSrc::addZone(ZonePtr zone) {
 
 MemoryDataSrc::FindResult
 MemoryDataSrc::findZone(const isc::dns::Name& name) const {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_FIND_ZONE).arg(name);
     return (FindResult(impl_->zone_table.findZone(name).code,
                        impl_->zone_table.findZone(name).zone));
 }
diff --git a/src/lib/datasrc/messagedef.mes b/src/lib/datasrc/messagedef.mes
new file mode 100644
index 0000000..2fc5c6b
--- /dev/null
+++ b/src/lib/datasrc/messagedef.mes
@@ -0,0 +1,498 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX DATASRC_
+$NAMESPACE isc::datasrc
+
+# \brief Messages for the data source library
+
+% CACHE_CREATE creating the hotspot cache
+Debug information that the hotspot cache was created at startup.
+
+% CACHE_DESTROY destroying the hotspot cache
+Debug information. The hotspot cache is being destroyed.
+
+% CACHE_INSERT inserting item '%1' into the cache
+Debug information. It means a new item is being inserted into the hotspot
+cache.
+
+% CACHE_OLD_FOUND older instance of cache item found, replacing
+Debug information. While inserting an item into the hotspot cache, an older
+instance of an item with the same name was found. The old instance will be
+removed. This should be directly followed by CACHE_REMOVE.
+
+% CACHE_FULL cache is full, dropping oldest
+Debug information. After inserting an item into the hotspot cache, the
+maximum number of items was exceeded, so the least recently used item will
+be dropped. This should be directly followed by CACHE_REMOVE.
+
+% CACHE_REMOVE removing '%1' from the cache
+Debug information. An item is being removed from the hotspot cache.
+
+% CACHE_NOT_FOUND the item '%1' was not found
+Debug information. It was attempted to look up an item in the hotspot cache,
+but it is not there.
+
+% CACHE_FOUND the item '%1' was found
+Debug information. An item was successfully looked up in the hotspot cache.
+
+% CACHE_EXPIRED the item '%1' is expired
+Debug information. There was an attempt to look up an item in the hotspot
+cache. And the item was actually there, but it was too old, so it was removed
+instead and nothing is reported (the external behaviour is the same as with
+CACHE_NOT_FOUND).
+
+% CACHE_SLOTS setting the cache size to '%1', dropping '%2' items
+The maximum allowed number of items of the hotspot cache is set to the given
+number. If there are too many, some of them will be dropped. The size of 0
+means no limit.
+
+% CACHE_ENABLE enabling the cache
+The hotspot cache is enabled from now on.
+
+% CACHE_DISABLE disabling the cache
+The hotspot cache is disabled from now on. It is not going to store
+information or return anything.
+
+% QUERY_SYNTH_CNAME synthesizing CNAME from DNAME on '%1'
+Debug information. While answering a query, a DNAME was met. The DNAME itself
+will be returned, but along with it a CNAME for clients which don't understand
+DNAMEs will be synthesized.
+
+% QUERY_EMPTY_DNAME the DNAME on '%1' is empty
+During an attempt to synthesize CNAME from this DNAME it was discovered the
+DNAME is empty (it has no records). This indicates problem with supplied data.
+
+% QUERY_GET_NS_ADDITIONAL addition of A/AAAA for '%1' requested by NS '%2'
+Debug information. While processing a query, a NS record was met. It
+references the mentioned address, so A/AAAA records for it are looked up
+and put it into the additional section.
+
+% QUERY_GET_MX_ADDITIONAL addition of A/AAAA for '%1' requested by MX '%2'
+Debug information. While processing a query, a MX record was met. It
+references the mentioned address, so A/AAAA records for it are looked up
+and put it into the additional section.
+
+% QUERY_FOLLOW_CNAME following CNAME at '%1'
+Debug information. The domain is a CNAME (or a DNAME and we created a CNAME
+for it already), so it's being followed.
+
+% QUERY_EMPTY_CNAME cNAME at '%1' is empty
+There was an CNAME and it was being followed. But it contains no records,
+so there's nowhere to go. There will be no answer. This indicates a problem
+with supplied data.
+We tried to follow
+
+% QUERY_TOO_MANY_CNAMES cNAME chain limit exceeded at '%1'
+A CNAME led to another CNAME and it led to another, and so on. After 16
+CNAMEs, the software gave up. Long CNAME chains are discouraged, and this
+might possibly be a loop as well. Note that some of the CNAMEs might have
+been synthesized from DNAMEs. This indicates problem with supplied data.
+
+% QUERY_CHECK_CACHE checking cache for '%1/%2'
+Debug information. While processing a query, lookup to the hotspot cache
+is being made.
+
+% QUERY_NO_CACHE_ANY_SIMPLE ignoring cache for ANY query (%1/%2 in %3 class)
+Debug information. The hotspot cache is ignored for ANY queries for consistency
+reasons.
+
+% QUERY_NO_CACHE_ANY_AUTH ignoring cache for ANY query (%1/%2 in %3 class)
+Debug information. The hotspot cache is ignored for authoritative ANY queries
+for consistency reasons.
+
+% DO_QUERY handling query for '%1/%2'
+Debug information. We're processing some internal query for given name and
+type.
+
+% QUERY_NO_ZONE no zone containing '%1' in class '%2'
+Lookup of domain failed because the data have no zone that contain the
+domain. Maybe someone sent a query to the wrong server for some reason.
+
+% QUERY_CACHED data for %1/%2 found in cache
+Debug information. The requested data were found in the hotspot cache, so
+no query is sent to the real data source.
+
+% QUERY_IS_SIMPLE simple query (%1/%2)
+Debug information. The last DO_QUERY is a simple query.
+
+% QUERY_IS_AUTH auth query (%1/%2)
+Debug information. The last DO_QUERY is an auth query.
+
+% QUERY_IS_GLUE glue query (%1/%2)
+Debug information. The last DO_QUERY is query for glue addresses.
+
+% QUERY_IS_NOGLUE query for non-glue addresses (%1/%2)
+Debug information. The last DO_QUERY is query for addresses that are not
+glue.
+
+% QUERY_IS_REF query for referral (%1/%2)
+Debug information. The last DO_QUERY is query for referral information.
+
+% QUERY_SIMPLE_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the simple query. 1 means some
+error, 2 is not implemented. The data source should have logged the specific
+error already.
+
+% QUERY_AUTH_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the authoritative query. 1 means
+some error, 2 is not implemented. The data source should have logged the
+specific error already.
+
+% QUERY_GLUE_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the glue query. 1 means some error,
+2 is not implemented. The data source should have logged the specific error
+already.
+
+% QUERY_NOGLUE_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the no-glue query. 1 means some
+error, 2 is not implemented. The data source should have logged the specific
+error already.
+
+% QUERY_REF_FAIL the underlying data source failed with %1
+The underlying data source failed to answer the query for referral information.
+1 means some error, 2 is not implemented. The data source should have logged
+the specific error already.
+
+% QUERY_INVALID_OP invalid query operation requested
+This indicates a programmer error. The DO_QUERY was called with unknown
+operation code.
+
+% QUERY_ADD_RRSET adding RRset '%1/%2' to message
+Debug information. An RRset is being added to the response message.
+
+% QUERY_COPY_AUTH copying authoritative section into message
+Debug information. The whole referral information is being copied into the
+response message.
+
+% QUERY_DELEGATION looking for delegation on the path to '%1'
+Debug information. The software is trying to identify delegation points on the
+way down to the given domain.
+
+% QUERY_ADD_SOA adding SOA of '%1'
+Debug information. A SOA record of the given zone is being added to the
+authority section of the response message.
+
+% QUERY_ADD_NSEC adding NSEC record for '%1'
+Debug information. A NSEC record covering this zone is being added.
+
+% QUERY_ADD_NSEC3 adding NSEC3 record of zone '%1'
+Debug information. A NSEC3 record for the given zone is being added to the
+response message.
+
+% QUERY_NO_DS_NSEC3 there's no DS record in the '%1' zone
+An attempt to add a NSEC3 record into the message failed, because the zone does
+not have any DS record. This indicates problem with the provided data.
+
+% QUERY_NO_DS_NSEC there's no DS record in the '%1' zone
+An attempt to add a NSEC record into the message failed, because the zone does
+not have any DS record. This indicates problem with the provided data.
+
+% QUERY_WILDCARD looking for a wildcard covering '%1'
+Debug information. A direct match wasn't found, so a wildcard covering the
+domain is being looked for now.
+
+% QUERY_WILDCARD_PROVENX_FAIL unable to prove nonexistence of '%1' (%2)
+While processing a wildcard, it wasn't possible to prove nonexistence of the
+given domain or record.  The code is 1 for error and 2 for not implemented.
+
+% QUERY_WILDCARD_REFERRAL unable to find referral info for '%1' (%2)
+While processing a wildcard, a referral was met. But it wasn't possible to get
+enough information for it.  The code is 1 for error, 2 for not implemented.
+
+% QUERY_PROCESS processing query '%1/%2' in the '%3' class
+Debug information. A sure query is being processed now.
+
+% QUERY_RRSIG unable to answer RRSIG query
+The server is unable to answer a direct query for RRSIG type, but was asked
+to do so.
+
+% QUERY_MISPLACED_TASK task of this type should not be here
+This indicates a programming error. A task was found in the internal task
+queue, but this kind of task wasn't designed to be inside the queue (it should
+be handled right away, not queued).
+
+% QUERY_TASK_FAIL task failed with %1
+The query subtask failed. The reason should have been reported by the subtask
+already. The code is 1 for error, 2 for not implemented.
+
+% QUERY_MISSING_NS missing NS records for '%1'
+NS records should have been put into the authority section. However, this zone
+has none. This indicates problem with provided data.
+
+% UNEXPECTED_QUERY_STATE unexpected query state
+This indicates a programming error. An internal task of unknown type was
+generated.
+
+% QUERY_FAIL query failed
+Some subtask of query processing failed. The reason should have been reported
+already. We are returning SERVFAIL.
+
+% QUERY_BAD_REFERRAL bad referral to '%1'
+The domain lives in another zone. But it is not possible to generate referral
+information for it.
+
+% QUERY_WILDCARD_FAIL error processing wildcard for '%1'
+During an attempt to cover the domain by a wildcard an error happened. The
+exact kind was hopefully already reported.
+
+% QUERY_MISSING_SOA the zone '%1' has no SOA
+The answer should have been a negative one (eg. of nonexistence of something).
+To do so, a SOA record should be put into the authority section, but the zone
+does not have one. This indicates problem with provided data.
+
+% QUERY_PROVENX_FAIL unable to prove nonexistence of '%1'
+The user wants DNSSEC and we discovered the entity doesn't exist (either
+domain or the record). But there was an error getting NSEC/NSEC3 record
+to prove the nonexistence.
+
+% QUERY_UNKNOWN_RESULT unknown result of subtask
+This indicates a programmer error. The answer of subtask doesn't look like
+anything known.
+
+% META_ADD adding a data source into meta data source
+Debug information. Yet another data source is being added into the meta data
+source. (probably at startup or reconfiguration)
+
+% META_ADD_CLASS_MISMATCH mismatch between classes '%1' and '%2'
+It was attempted to add a data source into a meta data source. But their
+classes do not match.
+
+% META_REMOVE removing data source from meta data source
+Debug information. A data source is being removed from meta data source.
+
+% MEM_ADD_WILDCARD adding wildcards for '%1'
+Debug information. Some special marks above each * in wildcard name are needed.
+They are being added now for this name.
+
+% MEM_CNAME_TO_NONEMPTY can't add CNAME to domain with other data in '%1'
+Someone or something tried to add a CNAME into a domain that already contains
+some other data. But the protocol forbids coexistence of CNAME with anything
+(RFC 1034, section 3.6.2). This indicates a problem with provided data.
+
+% MEM_CNAME_COEXIST can't add data to CNAME in domain '%1'
+This is the same problem as in MEM_CNAME_TO_NONEMPTY, but it happened the
+other way around -- adding some outher data to CNAME.
+
+% MEM_DNAME_NS dNAME and NS can't coexist in non-apex domain '%1'
+It was requested for DNAME and NS records to be put into the same domain
+which is not the apex (the top of the zone). This is forbidden by RFC
+2672, section 3. This indicates a problem with provided data.
+
+% MEM_SINGLETON trying to add multiple RRs for domain '%1' and type '%2'
+Some resource types are singletons -- only one is allowed in a domain
+(for example CNAME or SOA). This indicates a problem with provided data.
+
+% MEM_OUT_OF_ZONE domain '%1' doesn't belong to zone '%2'
+It was attempted to add the domain into a zone that shouldn't have it
+(eg. the domain is not subdomain of the zone origin). This indicates a
+problem with provided data.
+
+% MEM_WILDCARD_NS nS record in wildcard domain '%1'
+The software refuses to load NS records into a wildcard domain.  It isn't
+explicitly forbidden, but the protocol is ambiguous about how this should
+behave and BIND 9 refuses that as well. Please describe your intention using
+different tools.
+
+% MEM_WILDCARD_DNAME dNAME record in wildcard domain '%1'
+The software refuses to load DNAME records into a wildcard domain.  It isn't
+explicitly forbidden, but the protocol is ambiguous about how this should
+behave and BIND 9 refuses that as well. Please describe your intention using
+different tools.
+
+% MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
+Debug information. An RRset is being added to the in-memory data source.
+
+% MEM_DUP_RRSET duplicate RRset '%1/%2'
+An RRset is being inserted into in-memory data source for a second time.  The
+original version must be removed first. Note that loading master files where an
+RRset is split into multiple locations is not supported yet.
+
+% MEM_DNAME_ENCOUNTERED encountered a DNAME
+Debug information. While searching for the requested domain, a DNAME was
+encountered on the way.  This may lead to redirection to a different domain and
+stop the search.
+
+% MEM_NS_ENCOUNTERED encountered a NS
+Debug information. While searching for the requested domain, a NS was
+encountered on the way (a delegation). This may lead to stop of the search.
+
+% MEM_RENAME renaming RRset from '%1' to '%2'
+Debug information. A RRset is being generated from a different RRset (most
+probably a wildcard). So it must be renamed to whatever the user asked for. In
+fact, it's impossible to rename RRsets with our libraries, so a new one is
+created and all resource records are copied over.
+
+% MEM_FIND find '%1/%2'
+Debug information. A search for the requested RRset is being started.
+
+% MEM_DNAME_FOUND DNAME found at '%1'
+Debug information. A DNAME was found instead of the requested information.
+
+% MEM_DELEG_FOUND delegation found at '%1'
+Debug information. A delegation point was found above the requested record.
+
+% MEM_SUPER_STOP stopped at superdomain '%1', domain '%2' is empty
+Debug information. The search stopped at a superdomain of the requested
+domain. The domain is a empty nonterminal, therefore it is treated  as NXRRSET
+case (eg. the domain exists, but it doesn't have the requested record type).
+
+% MEM_WILDCARD_CANCEL wildcard match canceled for '%1'
+Debug information. A domain above wildcard was reached, but there's something
+below the requested domain. Therefore the wildcard doesn't apply here.  This
+behaviour is specified by RFC 1034, section 4.3.3
+
+% MEM_NOTFOUND requested domain '%1' not found
+Debug information. The requested domain does not exist.
+
+% MEM_DOMAIN_EMPTY requested domain '%1' is empty
+Debug information. The requested domain exists in the tree of domains, but
+it is empty. Therefore it doesn't contain the requested resource type.
+
+% MEM_EXACT_DELEGATION delegation at the exact domain '%1'
+Debug information. There's a NS record at the requested domain. This means
+this zone is not authoritative for the requested domain, but a delegation
+should be followed. The requested domain is an apex of some zone.
+
+% MEM_ANY_SUCCESS ANY query for '%1' successful
+Debug information. The domain was found and an ANY type query is being answered
+by providing everything found inside the domain.
+
+% MEM_SUCCESS query for '%1/%2' successful
+Debug information. The requested record was found.
+
+% MEM_CNAME CNAME at the domain '%1'
+Debug information. The requested domain is an alias to a different domain,
+returning the CNAME instead.
+
+% MEM_NXRRSET no such type '%1' at '%2'
+Debug information. The domain exists, but it doesn't hold any record of the
+requested type.
+
+% MEM_CREATE creating zone '%1' in '%2' class
+Debug information. A representation of a zone for the in-memory data source is
+being created.
+
+% MEM_DESTROY destroying zone '%1' in '%2' class
+Debug information. A zone from in-memory data source is being destroyed.
+
+% MEM_LOAD loading zone '%1' from file '%2'
+Debug information. The content of master file is being loaded into the memory.
+
+% MEM_SWAP swapping contents of two zone representations ('%1' and '%2')
+Debug information. The contents of two in-memory zones are being exchanged.
+This is usual practice to do some manipulation in exception-safe manner -- the
+new data are prepared in a different zone object and when it works, they are
+swapped. The old one contains the new data and the other one can be safely
+destroyed.
+
+% MEM_ADD_ZONE adding zone '%1/%2'
+Debug information. A zone is being added into the in-memory data source.
+
+% 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.
+
+% STATIC_CREATE creating the static datasource
+Debug information. The static data source (the one holding stuff like
+version.bind) is being created.
+
+% STATIC_BAD_CLASS static data source can handle CH only
+For some reason, someone asked the static data source a query that is not in
+the CH class.
+
+% STATIC_FIND looking for '%1/%2'
+Debug information. This resource record set is being looked up in the static
+data source.
+
+% SQLITE_FINDREC looking for record '%1/%2'
+Debug information. The SQLite data source is looking up records of given name
+and type in the database.
+
+% SQLITE_ENCLOSURE looking for zone containing '%1'
+Debug information. The SQLite data source is trying to identify, which zone
+should hold this domain.
+
+% SQLITE_ENCLOSURE_BAD_CLASS class mismatch looking for a zone ('%1' and '%2')
+The SQLite data source can handle only one class at a time and it was asked
+to identify which zone is holding data of a different class.
+
+% SQLITE_ENCLOSURE_NOTFOUND no zone contains it
+Debug information. The last SQLITE_ENCLOSURE query was unsuccessful, there's
+no such zone in our data.
+
+% SQLITE_PREVIOUS looking for name previous to '%1'
+Debug information. We're trying to look up name preceding the supplied one.
+
+% SQLITE_PREVIOUS_NO_ZONE no zone containing '%1'
+The SQLite data source tried to identify name preceding this one. But this
+one is not contained in any zone in the data source.
+
+% SQLITE_FIND_NSEC3 looking for NSEC3 in zone '%1' for hash '%2'
+Debug information. We're trying to look up a NSEC3 record in the SQLite data
+source.
+
+% SQLITE_FIND_NSEC3_NO_ZONE no such zone '%1'
+The SQLite data source was asked to provide a NSEC3 record for given zone.
+But it doesn't contain that zone.
+
+% SQLITE_FIND looking for RRset '%1/%2'
+Debug information. The SQLite data source is looking up a resource record
+set.
+
+% SQLITE_FIND_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
+The SQLite data source was looking up an RRset, but the data source contains
+different class than the query was for.
+
+% SQLITE_FINDEXACT looking for exact RRset '%1/%2'
+Debug information. The SQLite data source is looking up an exact resource
+record.
+
+% SQLITE_FINDEXACT_BAD_CLASS class mismatch looking for an RRset ('%1' and '%2')
+The SQLite data source was looking up an exact RRset, but the data source
+contains different class than the query was for.
+
+% SQLITE_FINDADDRS looking for A/AAAA addresses for '%1'
+Debug information. The data source is looking up the addresses for given
+domain name.
+
+% SQLITE_FINDADDRS_BAD_CLASS class mismatch looking for addresses ('%1' and '%2')
+The SQLite data source was looking up A/AAAA addresses, but the data source
+contains different class than the query was for.
+
+% SQLITE_FINDREF looking for referral at '%1'
+Debug information. The SQLite data source is identifying if this domain is
+a referral and where it goes.
+
+% SQLITE_FINDREF_BAD_CLASS class mismatch looking for referral ('%1' and '%2')
+The SQLite data source was trying to identify, if there's a referral. But
+it contains different class than the query was for.
+
+% SQLITE_CREATE sQLite data source created
+Debug information. An instance of SQLite data source is being created.
+
+% SQLITE_DESTROY sQLite data source destroyed
+Debug information. An instance of SQLite data source is being destroyed.
+
+% SQLITE_SETUP setting up SQLite database
+The database for SQLite data source was found empty. It is assumed this is the
+first run and it is being initialized with current schema.  It'll still contain
+no data, but it will be ready for use.
+
+% SQLITE_OPEN opening SQLite database '%1'
+Debug information. The SQLite data source is loading an SQLite database in
+the provided file.
+
+% SQLITE_CLOSE closing SQLite database
+Debug information. The SQLite data source is closing the database file.
diff --git a/src/lib/datasrc/sqlite3_datasrc.cc b/src/lib/datasrc/sqlite3_datasrc.cc
index ab910ba..22f035b 100644
--- a/src/lib/datasrc/sqlite3_datasrc.cc
+++ b/src/lib/datasrc/sqlite3_datasrc.cc
@@ -18,6 +18,7 @@
 #include <sqlite3.h>
 
 #include <datasrc/sqlite3_datasrc.h>
+#include <datasrc/logger.h>
 
 #include <dns/rrttl.h>
 #include <dns/rdata.h>
@@ -227,6 +228,8 @@ Sqlite3DataSrc::findRecords(const Name& name, const RRType& rdtype,
                             RRsetList& target, const Name* zonename,
                             const Mode mode, uint32_t& flags) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_SQLITE_FINDREC).arg(name).
+        arg(rdtype);
     flags = 0;
     int zone_id = (zonename == NULL) ? findClosest(name, NULL) :
         findClosest(*zonename, NULL);
@@ -345,12 +348,17 @@ Sqlite3DataSrc::findClosest(const Name& name, unsigned int* position) const {
 
 void
 Sqlite3DataSrc::findClosestEnclosure(DataSrcMatch& match) const {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_ENCLOSURE).
+        arg(match.getName());
     if (match.getClass() != getClass() && match.getClass() != RRClass::ANY()) {
+        LOG_ERROR(logger, DATASRC_SQLITE_ENCLOSURE_BAD_CLASS).arg(getClass()).
+            arg(match.getClass());
         return;
     }
 
     unsigned int position;
     if (findClosest(match.getName(), &position) == -1) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_ENCLOSURE_NOTFOUND);
         return;
     }
 
@@ -362,9 +370,11 @@ Sqlite3DataSrc::findPreviousName(const Name& qname,
                                  Name& target,
                                  const Name* zonename) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_PREVIOUS).arg(qname);
     const int zone_id = (zonename == NULL) ?
         findClosest(qname, NULL) : findClosest(*zonename, NULL);
     if (zone_id < 0) {
+        LOG_ERROR(logger, DATASRC_SQLITE_PREVIOUS_NO_ZONE).arg(qname.toText());
         return (ERROR);
     }
     
@@ -402,8 +412,11 @@ Sqlite3DataSrc::findCoveringNSEC3(const Name& zonename,
                                   string& hashstr,
                                   RRsetList& target) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FIND_NSEC3).
+        arg(zonename).arg(hashstr);
     const int zone_id = findClosest(zonename, NULL);
     if (zone_id < 0) {
+        LOG_ERROR(logger, DATASRC_SQLITE_FIND_NSEC3_NO_ZONE).arg(zonename);
         return (ERROR);
     }
 
@@ -484,7 +497,11 @@ Sqlite3DataSrc::findRRset(const Name& qname,
                           uint32_t& flags,
                           const Name* zonename) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FIND).arg(qname).
+        arg(qtype);
     if (qclass != getClass() && qclass != RRClass::ANY()) {
+        LOG_ERROR(logger, DATASRC_SQLITE_FIND_BAD_CLASS).arg(getClass()).
+            arg(qclass);
         return (ERROR);
     }
     findRecords(qname, qtype, target, zonename, NORMAL, flags);
@@ -499,7 +516,11 @@ Sqlite3DataSrc::findExactRRset(const Name& qname,
                                uint32_t& flags,
                                const Name* zonename) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FINDEXACT).arg(qname).
+        arg(qtype);
     if (qclass != getClass() && qclass != RRClass::ANY()) {
+        LOG_ERROR(logger, DATASRC_SQLITE_FINDEXACT_BAD_CLASS).arg(getClass()).
+            arg(qclass);
         return (ERROR);
     }
     findRecords(qname, qtype, target, zonename, NORMAL, flags);
@@ -523,7 +544,10 @@ Sqlite3DataSrc::findAddrs(const Name& qname,
                           uint32_t& flags,
                           const Name* zonename) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FINDADDRS).arg(qname);
     if (qclass != getClass() && qclass != RRClass::ANY()) {
+        LOG_ERROR(logger, DATASRC_SQLITE_FINDADDRS_BAD_CLASS).arg(getClass()).
+            arg(qclass);
         return (ERROR);
     }
     findRecords(qname, RRType::ANY(), target, zonename, ADDRESS, flags);
@@ -537,8 +561,11 @@ Sqlite3DataSrc::findReferral(const Name& qname,
                              uint32_t& flags,
                              const Name* zonename) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_SQLITE_FINDREF).arg(qname);
     if (qclass != getClass() && qclass != RRClass::ANY()) {
-            return (ERROR);
+        LOG_ERROR(logger, DATASRC_SQLITE_FINDREF_BAD_CLASS).arg(getClass()).
+            arg(qclass);
+        return (ERROR);
     }
     findRecords(qname, RRType::ANY(), target, zonename, DELEGATION, flags);
     return (SUCCESS);
@@ -546,9 +573,12 @@ Sqlite3DataSrc::findReferral(const Name& qname,
 
 Sqlite3DataSrc::Sqlite3DataSrc() :
     dbparameters(new Sqlite3Parameters)
-{}
+{
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CREATE);
+}
 
 Sqlite3DataSrc::~Sqlite3DataSrc() {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_DESTROY);
     if (dbparameters->db_ != NULL) {
         close();
     }
@@ -635,6 +665,7 @@ checkAndSetupSchema(Sqlite3Initializer* initializer) {
         initializer->params_.version_ = sqlite3_column_int(prepared, 0);
         sqlite3_finalize(prepared);
     } else {
+        logger.info(DATASRC_SQLITE_SETUP);
         if (prepared != NULL) {
             sqlite3_finalize(prepared);
         }
@@ -664,6 +695,7 @@ checkAndSetupSchema(Sqlite3Initializer* initializer) {
 //
 void
 Sqlite3DataSrc::open(const string& name) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_OPEN).arg(name);
     if (dbparameters->db_ != NULL) {
         isc_throw(DataSourceError, "Duplicate SQLite open with " << name);
     }
@@ -683,6 +715,7 @@ Sqlite3DataSrc::open(const string& name) {
 //
 DataSrc::Result
 Sqlite3DataSrc::close(void) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_CLOSE);
     if (dbparameters->db_ == NULL) {
         isc_throw(DataSourceError,
                   "SQLite data source is being closed before open");
diff --git a/src/lib/datasrc/static_datasrc.cc b/src/lib/datasrc/static_datasrc.cc
index 025078a..dee14b9 100644
--- a/src/lib/datasrc/static_datasrc.cc
+++ b/src/lib/datasrc/static_datasrc.cc
@@ -26,6 +26,7 @@
 
 #include <datasrc/data_source.h>
 #include <datasrc/static_datasrc.h>
+#include <datasrc/logger.h>
 
 using namespace std;
 using namespace isc::dns;
@@ -112,6 +113,7 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
 }
 
 StaticDataSrc::StaticDataSrc() {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_STATIC_CREATE);
     setClass(RRClass::CH());
     impl_ = new StaticDataSrcImpl;
 }
@@ -155,8 +157,11 @@ StaticDataSrc::findRRset(const Name& qname,
                          RRsetList& target, uint32_t& flags,
                          const Name* const zonename) const
 {
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_STATIC_FIND).arg(qname).
+        arg(qtype);
     flags = 0;
     if (qclass != getClass() && qclass != RRClass::ANY()) {
+        LOG_ERROR(logger, DATASRC_STATIC_BAD_CLASS);
         return (ERROR);
     }
 
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index f09b4b7..ad4374a 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -27,15 +27,17 @@ run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
 run_unittests_SOURCES += rbtree_unittest.cc
 run_unittests_SOURCES += zonetable_unittest.cc
 run_unittests_SOURCES += memory_datasrc_unittest.cc
+run_unittests_SOURCES += logger_unittest.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
-run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
 run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
-run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
 endif
 
 noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/datasrc/tests/logger_unittest.cc b/src/lib/datasrc/tests/logger_unittest.cc
new file mode 100644
index 0000000..df5a41c
--- /dev/null
+++ b/src/lib/datasrc/tests/logger_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2010  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 <gtest/gtest.h>
+
+#include <datasrc/logger.h>
+
+using namespace isc::datasrc;
+
+namespace {
+
+TEST(CacheLogger, name) {
+    // This does not check the name only, but the fact the logger is created
+    // The dot is because of empty root logger
+    std::string name(logger.getName());
+    EXPECT_EQ(name.size() - 8, name.rfind(".datasrc")) <<
+        "Wrong logger name: " << name;
+}
+
+}
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 8a657da..fcc53e9 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -601,6 +601,7 @@ private:
 /// that ongoing state information will not be lost if the object
 /// that originated the asynchronous call falls out of scope.
 typedef boost::shared_ptr<Message> MessagePtr;
+typedef boost::shared_ptr<const Message> ConstMessagePtr;
 
 /// Insert the \c Message as a string into stream.
 ///
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 9162f4e..1c9afc7 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -6,6 +6,9 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 pyexec_LTLIBRARIES = pydnspp.la
 pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc
+pydnspp_la_SOURCES += rcode_python.cc rcode_python.h
+pydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
+pydnspp_la_SOURCES += tsigerror_python_inc.cc
 pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index e3cc53f..381b7bc 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -336,15 +336,15 @@ Message_getRcode(s_Message* self) {
 
     rcode = static_cast<s_Rcode*>(rcode_type.tp_alloc(&rcode_type, 0));
     if (rcode != NULL) {
-        rcode->rcode = NULL;
+        rcode->cppobj = NULL;
         try {
-            rcode->rcode = new Rcode(self->message->getRcode());
+            rcode->cppobj = new Rcode(self->message->getRcode());
         } catch (const InvalidMessageOperation& imo) {
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
         } catch (...) {
             PyErr_SetString(po_IscException, "Unexpected exception");
         }
-        if (rcode->rcode == NULL) {
+        if (rcode->cppobj == NULL) {
             Py_DECREF(rcode);
             return (NULL);
         }
@@ -360,7 +360,7 @@ Message_setRcode(s_Message* self, PyObject* args) {
         return (NULL);
     }
     try {
-        self->message->setRcode(*rcode->rcode);
+        self->message->setRcode(*rcode->cppobj);
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 2138198..0be346e 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -32,20 +32,32 @@
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
+
 #include <dns/exceptions.h>
 #include <dns/name.h>
 #include <dns/messagerenderer.h>
 
-#include <dns/python/pydnspp_common.h>
+#include "pydnspp_common.h"
 
+namespace isc {
+namespace dns {
+namespace python {
 // For our 'general' isc::Exceptions
-static PyObject* po_IscException;
-static PyObject* po_InvalidParameter;
+PyObject* po_IscException;
+PyObject* po_InvalidParameter;
 
 // For our own isc::dns::Exception
-static PyObject* po_DNSMessageBADVERS;
+PyObject* po_DNSMessageBADVERS;
+}
+}
+}
+
+#include "rcode_python.h"
+#include "tsigerror_python.h"
 
 // order is important here!
+using namespace isc::dns::python;
+
 #include <dns/python/messagerenderer_python.cc>
 #include <dns/python/name_python.cc>           // needs Messagerenderer
 #include <dns/python/rrclass_python.cc>        // needs Messagerenderer
@@ -58,14 +70,14 @@ static PyObject* po_DNSMessageBADVERS;
 #include <dns/python/tsigkey_python.cc>        // needs Name
 #include <dns/python/tsig_python.cc>           // needs tsigkey
 #include <dns/python/opcode_python.cc>
-#include <dns/python/rcode_python.cc>
 #include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
 #include <dns/python/message_python.cc>        // needs RRset, Question
 
 //
 // Definition of the module
 //
-static PyModuleDef pydnspp = {
+namespace {
+PyModuleDef pydnspp = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     "pydnspp",
     "Python bindings for the classes in the isc::dns namespace.\n\n"
@@ -80,10 +92,11 @@ static PyModuleDef pydnspp = {
     NULL,
     NULL
 };
+}
 
 PyMODINIT_FUNC
 PyInit_pydnspp(void) {
-    PyObject *mod = PyModule_Create(&pydnspp);
+    PyObject* mod = PyModule_Create(&pydnspp);
     if (mod == NULL) {
         return (NULL);
     }
@@ -154,6 +167,10 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_TSIGError(mod)) {
+        return (NULL);
+    }
+
     if (!initModulePart_TSIGContext(mod)) {
         return (NULL);
     }
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index 6c26367..8ca763a 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -15,6 +15,9 @@
 #include <Python.h>
 #include <pydnspp_common.h>
 
+namespace isc {
+namespace dns {
+namespace python {
 int
 readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
     PyObject* el = NULL;
@@ -44,8 +47,15 @@ readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
 }
 
 
-void addClassVariable(PyTypeObject& c, const char* name,
-                      PyObject* obj)
-{
-    PyDict_SetItemString(c.tp_dict, name, obj);
+int
+addClassVariable(PyTypeObject& c, const char* name, PyObject* obj) {
+    if (obj == NULL) {
+        PyErr_SetString(PyExc_ValueError,
+                        "NULL object is specified for a class variable");
+        return (-1);
+    }
+    return (PyDict_SetItemString(c.tp_dict, name, obj));
+}
+}
+}
 }
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 32e2b78..ed90998 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -15,9 +15,22 @@
 #ifndef __LIBDNS_PYTHON_COMMON_H
 #define __LIBDNS_PYTHON_COMMON_H 1
 
-//
-// Shared functions for python/c API
-//
+#include <Python.h>
+
+#include <stdexcept>
+#include <string>
+
+#include <util/python/pycppwrapper_util.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+// For our 'general' isc::Exceptions
+extern PyObject* po_IscException;
+extern PyObject* po_InvalidParameter;
+
+// For our own isc::dns::Exception
+extern PyObject* po_DNSMessageBADVERS;
 
 // This function reads 'bytes' from a sequence
 // This sequence can be anything that implements the Sequence interface,
@@ -31,6 +44,12 @@
 // case nothing is removed
 int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
 
-void addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
-
+int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
+} // namespace python
+} // namespace dns
+} // namespace isc
 #endif // __LIBDNS_PYTHON_COMMON_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rcode_python.cc b/src/lib/dns/python/rcode_python.cc
index b80a93c..b594ad3 100644
--- a/src/lib/dns/python/rcode_python.cc
+++ b/src/lib/dns/python/rcode_python.cc
@@ -12,9 +12,17 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
 #include <dns/rcode.h>
 
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+
 using namespace isc::dns;
+using namespace isc::dns::python;
 
 //
 // Declaration of the custom exceptions (None for this class)
@@ -27,25 +35,14 @@ using namespace isc::dns;
 // and static wrappers around the methods we export), a list of methods,
 // and a type description
 
-namespace {
 //
 // Rcode
 //
 
-// We added a helper variable static_code here
-// Since we can create Rcodes dynamically with Rcode(int), but also
-// use the static globals (Rcode::NOERROR() etc), we use this
-// variable to see if the code came from one of the latter, in which
-// case Rcode_destroy should not free it (the other option is to
-// allocate new Rcodes for every use of the static ones, but this
-// seems more efficient).
-class s_Rcode : public PyObject {
-public:
-    s_Rcode() : rcode(NULL), static_code(false) {}
-    const Rcode* rcode;
-    bool static_code;
-};
+// Trivial constructor.
+s_Rcode::s_Rcode() : cppobj(NULL), static_code(false) {}
 
+namespace {
 int Rcode_init(s_Rcode* const self, PyObject* args);
 void Rcode_destroy(s_Rcode* const self);
 
@@ -118,57 +115,6 @@ PyMethodDef Rcode_methods[] = {
     { NULL, NULL, 0, NULL }
 };
 
-PyTypeObject rcode_type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "pydnspp.Rcode",
-    sizeof(s_Rcode),                    // tp_basicsize
-    0,                                  // tp_itemsize
-    (destructor)Rcode_destroy,          // tp_dealloc
-    NULL,                               // tp_print
-    NULL,                               // tp_getattr
-    NULL,                               // tp_setattr
-    NULL,                               // tp_reserved
-    NULL,                               // tp_repr
-    NULL,                               // tp_as_number
-    NULL,                               // tp_as_sequence
-    NULL,                               // tp_as_mapping
-    NULL,                               // tp_hash 
-    NULL,                               // tp_call
-    Rcode_str,                          // tp_str
-    NULL,                               // tp_getattro
-    NULL,                               // tp_setattro
-    NULL,                               // tp_as_buffer
-    Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "The Rcode class objects represent standard RCODEs"
-    "of the header section of DNS messages.",
-    NULL,                               // tp_traverse
-    NULL,                               // tp_clear
-    (richcmpfunc)Rcode_richcmp,         // tp_richcompare
-    0,                                  // tp_weaklistoffset
-    NULL,                               // tp_iter
-    NULL,                               // tp_iternext
-    Rcode_methods,                      // tp_methods
-    NULL,                               // tp_members
-    NULL,                               // tp_getset
-    NULL,                               // tp_base
-    NULL,                               // tp_dict
-    NULL,                               // tp_descr_get
-    NULL,                               // tp_descr_set
-    0,                                  // tp_dictoffset
-    (initproc)Rcode_init,               // tp_init
-    NULL,                               // tp_alloc
-    PyType_GenericNew,                  // tp_new
-    NULL,                               // tp_free
-    NULL,                               // tp_is_gc
-    NULL,                               // tp_bases
-    NULL,                               // tp_mro
-    NULL,                               // tp_cache
-    NULL,                               // tp_subclasses
-    NULL,                               // tp_weaklist
-    NULL,                               // tp_del
-    0                                   // tp_version_tag
-};
-
 int
 Rcode_init(s_Rcode* const self, PyObject* args) {
     long code = 0;
@@ -193,9 +139,9 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
     }
     try {
         if (ext_code == -1) {
-            self->rcode = new Rcode(code);
+            self->cppobj = new Rcode(code);
         } else {
-            self->rcode = new Rcode(code, ext_code);
+            self->cppobj = new Rcode(code, ext_code);
         }
         self->static_code = false;
     } catch (const isc::OutOfRange& ex) {
@@ -211,27 +157,27 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
 void
 Rcode_destroy(s_Rcode* const self) {
     // Depending on whether we created the rcode or are referring
-    // to a global one, we do or do not delete self->rcode here
+    // to a global one, we do or do not delete self->cppobj here
     if (!self->static_code) {
-        delete self->rcode;
+        delete self->cppobj;
     }
-    self->rcode = NULL;
+    self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
 }
 
 PyObject*
 Rcode_getCode(const s_Rcode* const self) {
-    return (Py_BuildValue("I", self->rcode->getCode()));
+    return (Py_BuildValue("I", self->cppobj->getCode()));
 }
 
 PyObject*
 Rcode_getExtendedCode(const s_Rcode* const self) {
-    return (Py_BuildValue("I", self->rcode->getExtendedCode()));
+    return (Py_BuildValue("I", self->cppobj->getExtendedCode()));
 }
 
 PyObject*
 Rcode_toText(const s_Rcode* const self) {
-    return (Py_BuildValue("s", self->rcode->toText().c_str()));
+    return (Py_BuildValue("s", self->cppobj->toText().c_str()));
 }
 
 PyObject*
@@ -245,7 +191,7 @@ PyObject*
 Rcode_createStatic(const Rcode& rcode) {
     s_Rcode* ret = PyObject_New(s_Rcode, &rcode_type);
     if (ret != NULL) {
-        ret->rcode = &rcode;
+        ret->cppobj = &rcode;
         ret->static_code = true;
     }
     return (ret);
@@ -357,10 +303,10 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
         return (NULL);
     case Py_EQ:
-        c = (*self->rcode == *other->rcode);
+        c = (*self->cppobj == *other->cppobj);
         break;
     case Py_NE:
-        c = (*self->rcode != *other->rcode);
+        c = (*self->cppobj != *other->cppobj);
         break;
     case Py_GT:
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
@@ -374,6 +320,61 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
     else
         Py_RETURN_FALSE;
 }
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject rcode_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.Rcode",
+    sizeof(s_Rcode),                    // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)Rcode_destroy,          // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash
+    NULL,                               // tp_call
+    Rcode_str,                          // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The Rcode class objects represent standard RCODEs"
+    "of the header section of DNS messages.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    reinterpret_cast<richcmpfunc>(Rcode_richcmp),         // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    Rcode_methods,                      // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    (initproc)Rcode_init,               // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
 
 // Module Initialization, all statics are initialized here
 bool
@@ -428,4 +429,6 @@ initModulePart_Rcode(PyObject* mod) {
 
     return (true);
 }
-} // end of unnamed namespace
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/rcode_python.h b/src/lib/dns/python/rcode_python.h
new file mode 100644
index 0000000..9b5e699
--- /dev/null
+++ b/src/lib/dns/python/rcode_python.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_RCODE_H
+#define __PYTHON_RCODE_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Rcode;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object.
+//
+// We added a helper variable static_code here
+// Since we can create Rcodes dynamically with Rcode(int), but also
+// use the static globals (Rcode::NOERROR() etc), we use this
+// variable to see if the code came from one of the latter, in which
+// case Rcode_destroy should not free it (the other option is to
+// allocate new Rcodes for every use of the static ones, but this
+// seems more efficient).
+//
+// Follow-up note: we don't have to use the proxy function in the python lib;
+// we can just define class specific constants directly (see TSIGError).
+// We should make this cleanup later.
+class s_Rcode : public PyObject {
+public:
+    s_Rcode();
+    const Rcode* cppobj;
+    bool static_code;
+};
+
+extern PyTypeObject rcode_type;
+
+bool initModulePart_Rcode(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RCODE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 9ee98c7..1149a47 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -12,6 +12,7 @@ PYTESTS += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_python_test.py
 PYTESTS += tsig_python_test.py
+PYTESTS += tsigerror_python_test.py
 PYTESTS += tsigkey_python_test.py
 
 EXTRA_DIST = $(PYTESTS)
diff --git a/src/lib/dns/python/tests/tsigerror_python_test.py b/src/lib/dns/python/tests/tsigerror_python_test.py
new file mode 100644
index 0000000..a968b6b
--- /dev/null
+++ b/src/lib/dns/python/tests/tsigerror_python_test.py
@@ -0,0 +1,97 @@
+# Copyright (C) 2011  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.
+
+import unittest
+import sys
+from pydnspp import *
+
+class TSIGErrorTest(unittest.TestCase):
+    def test_from_code(self):
+        self.assertEqual(0, TSIGError(0).get_code())
+        self.assertEqual(18, TSIGError(18).get_code())
+        self.assertEqual(65535, TSIGError(65535).get_code())
+        self.assertRaises(ValueError, TSIGError, 65536)
+        self.assertRaises(ValueError, TSIGError, -1)
+        self.assertRaises(TypeError, TSIGError, "not yet supported")
+
+    def test_from_rcode(self):
+        # We use RCODE for code values from 0-15.
+        self.assertEqual(0, TSIGError(Rcode.NOERROR()).get_code())
+        self.assertEqual(15, TSIGError(Rcode(15)).get_code())
+
+        # From error code 16 TSIG errors define a separate space, so passing
+        # corresponding RCODE for such code values should be prohibited.
+        self.assertRaises(ValueError, TSIGError, Rcode(16))
+
+    def test_constants(self):
+        # We'll only test arbitrarily chosen subsets of the codes.
+        # This class is quite simple, so it should be suffice.
+        self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError(16).get_code())
+        self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError(17).get_code())
+        self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError(18).get_code())
+
+        self.assertEqual(0, TSIGError.NOERROR.get_code())
+        self.assertEqual(9, TSIGError.NOTAUTH.get_code())
+        self.assertEqual(14, TSIGError.RESERVED14.get_code())
+        self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError.BAD_SIG.get_code())
+        self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError.BAD_KEY.get_code())
+        self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError.BAD_TIME.get_code())
+
+    def test_equal(self):
+        self.assertTrue(TSIGError.NOERROR == TSIGError(Rcode.NOERROR()))
+        self.assertTrue(TSIGError(Rcode.NOERROR()) == TSIGError.NOERROR)
+
+        self.assertTrue(TSIGError.BAD_SIG == TSIGError(16))
+        self.assertTrue(TSIGError(16) == TSIGError.BAD_SIG)
+
+    def test_nequal(self):
+        self.assertTrue(TSIGError.BAD_KEY != TSIGError(Rcode.NOERROR()))
+        self.assertTrue(TSIGError(Rcode.NOERROR()) != TSIGError.BAD_KEY)
+
+    def test_to_text(self):
+        # TSIGError derived from the standard Rcode
+        self.assertEqual("NOERROR", TSIGError(Rcode.NOERROR()).to_text())
+
+        # Well known TSIG errors
+        self.assertEqual("BADSIG", TSIGError.BAD_SIG.to_text())
+        self.assertEqual("BADKEY", TSIGError.BAD_KEY.to_text())
+        self.assertEqual("BADTIME", TSIGError.BAD_TIME.to_text())
+
+        # Unknown (or not yet supported) codes.  Simply converted as numeric.
+        self.assertEqual("19", TSIGError(19).to_text());
+        self.assertEqual("65535", TSIGError(65535).to_text());
+
+        # also check str() works same way
+        self.assertEqual("NOERROR", str(TSIGError(Rcode.NOERROR())))
+        self.assertEqual("BADSIG", str(TSIGError.BAD_SIG))
+
+    def test_to_rcode(self):
+        # TSIGError derived from the standard Rcode
+        self.assertEqual(Rcode.NOERROR(), TSIGError(Rcode.NOERROR()).to_rcode())
+
+        # Well known TSIG errors
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_SIG.to_rcode())
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_KEY.to_rcode())
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_TIME.to_rcode())
+
+        # Unknown (or not yet supported) codes are treated as SERVFAIL.
+        self.assertEqual(Rcode.SERVFAIL(), TSIGError(19).to_rcode())
+        self.assertEqual(Rcode.SERVFAIL(), TSIGError(65535).to_rcode())
+
+        # Check there's no redundant refcount (which would cause leak)
+        self.assertEqual(1, sys.getrefcount(TSIGError.BAD_SIG.to_rcode()))
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/src/lib/dns/python/tests/tsigkey_python_test.py b/src/lib/dns/python/tests/tsigkey_python_test.py
index 72a1e01..516bea4 100644
--- a/src/lib/dns/python/tests/tsigkey_python_test.py
+++ b/src/lib/dns/python/tests/tsigkey_python_test.py
@@ -71,6 +71,9 @@ class TSIGKeyTest(unittest.TestCase):
 
 class TSIGKeyRingTest(unittest.TestCase):
     key_name = Name('example.com')
+    md5_name = Name('hmac-md5.sig-alg.reg.int')
+    sha1_name = Name('hmac-sha1')
+    sha256_name = Name('hmac-sha256')
     secret = b'someRandomData'
 
     def setUp(self):
@@ -155,18 +158,26 @@ class TSIGKeyRingTest(unittest.TestCase):
 
     def test_find(self):
         self.assertEqual((TSIGKeyRing.NOTFOUND, None),
-                         self.keyring.find(self.key_name))
+                         self.keyring.find(self.key_name, self.md5_name))
 
         self.assertEqual(TSIGKeyRing.SUCCESS,
                          self.keyring.add(TSIGKey(self.key_name,
-                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.sha256_name,
                                                   self.secret)))
-        (code, key) = self.keyring.find(self.key_name)
+        (code, key) = self.keyring.find(self.key_name, self.sha256_name)
         self.assertEqual(TSIGKeyRing.SUCCESS, code)
         self.assertEqual(self.key_name, key.get_key_name())
         self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
         self.assertEqual(self.secret, key.get_secret())
 
+        (code, key) = self.keyring.find(Name('different-key.example'),
+                                        self.sha256_name)
+        self.assertEqual(TSIGKeyRing.NOTFOUND, code)
+        self.assertEqual(None, key)
+        (code, key) = self.keyring.find(self.key_name, self.md5_name)
+        self.assertEqual(TSIGKeyRing.NOTFOUND, code)
+        self.assertEqual(None, key)
+
         self.assertRaises(TypeError, self.keyring.find, 1)
         self.assertRaises(TypeError, self.keyring.find, 'should be a name')
         self.assertRaises(TypeError, self.keyring.find, self.key_name, 0)
@@ -174,24 +185,28 @@ class TSIGKeyRingTest(unittest.TestCase):
     def test_find_from_some(self):
         self.assertEqual(TSIGKeyRing.SUCCESS,
                          self.keyring.add(TSIGKey(self.key_name,
-                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.sha256_name,
                                                   self.secret)))
         self.assertEqual(TSIGKeyRing.SUCCESS,
                          self.keyring.add(TSIGKey(Name('another.example'),
-                                                  TSIGKey.HMACMD5_NAME,
+                                                  self.md5_name,
                                                   self.secret)))
         self.assertEqual(TSIGKeyRing.SUCCESS,
                          self.keyring.add(TSIGKey(Name('more.example'),
-                                                  TSIGKey.HMACSHA1_NAME,
+                                                  self.sha1_name,
                                                   self.secret)))
 
-        (code, key) = self.keyring.find(Name('another.example'))
+        (code, key) = self.keyring.find(Name('another.example'), self.md5_name)
         self.assertEqual(TSIGKeyRing.SUCCESS, code)
         self.assertEqual(Name('another.example'), key.get_key_name())
         self.assertEqual(TSIGKey.HMACMD5_NAME, key.get_algorithm_name())
 
         self.assertEqual((TSIGKeyRing.NOTFOUND, None),
-                         self.keyring.find(Name('noexist.example')))
+                         self.keyring.find(Name('noexist.example'),
+                                           self.sha1_name))
+        self.assertEqual((TSIGKeyRing.NOTFOUND, None),
+                         self.keyring.find(Name('another.example'),
+                                           self.sha1_name))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/src/lib/dns/python/tsig_python.cc b/src/lib/dns/python/tsig_python.cc
index 2e6d986..7b6ba01 100644
--- a/src/lib/dns/python/tsig_python.cc
+++ b/src/lib/dns/python/tsig_python.cc
@@ -26,7 +26,6 @@ using namespace isc::dns;
 
 namespace {
 // The s_* Class simply covers one instantiation of the object
-
 class s_TSIGContext : public PyObject {
 public:
     TSIGContext* tsig_ctx;
diff --git a/src/lib/dns/python/tsigerror_python.cc b/src/lib/dns/python/tsigerror_python.cc
new file mode 100644
index 0000000..e973772
--- /dev/null
+++ b/src/lib/dns/python/tsigerror_python.cc
@@ -0,0 +1,362 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/tsigerror.h>
+
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+#include "tsigerror_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// TSIGError
+//
+
+// Trivial constructor.
+s_TSIGError::s_TSIGError() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "tsigerror_python_inc.cc"
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGError, TSIGError> TSIGErrorContainer;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIGError_init(s_TSIGError* self, PyObject* args);
+void TSIGError_destroy(s_TSIGError* self);
+
+// These are the functions we export
+PyObject* TSIGError_getCode(const s_TSIGError* const self);
+PyObject* TSIGError_toText(const s_TSIGError* const self);
+PyObject* TSIGError_toRcode(const s_TSIGError* const self);
+PyObject* TSIGError_str(PyObject* self);
+PyObject* TSIGError_richcmp(const s_TSIGError* const self,
+                            const s_TSIGError* const other, int op);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIGError_methods[] = {
+    { "get_code", reinterpret_cast<PyCFunction>(TSIGError_getCode),
+      METH_NOARGS,
+      TSIGError_getCode_doc },
+    { "to_text", reinterpret_cast<PyCFunction>(TSIGError_toText), METH_NOARGS,
+      TSIGError_toText_doc },
+    { "to_rcode", reinterpret_cast<PyCFunction>(TSIGError_toRcode),
+      METH_NOARGS,
+      TSIGError_toRcode_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+int
+TSIGError_init(s_TSIGError* self, PyObject* args) {
+    try {
+        // Constructor from the code value
+        long code = 0;
+        if (PyArg_ParseTuple(args, "l", &code)) {
+            if (code < 0 || code > 0xffff) {
+                PyErr_SetString(PyExc_ValueError, "TSIG error out of range");
+                return (-1);
+            }
+            self->cppobj = new TSIGError(code);
+            return (0);
+        }
+
+        // Constructor from Rcode
+        s_Rcode* py_rcode;
+        if (PyArg_ParseTuple(args, "O!", &rcode_type, &py_rcode)) {
+            self->cppobj = new TSIGError(*py_rcode->cppobj);
+            return (0);
+        }
+    } catch (const isc::OutOfRange& ex) {
+        const string ex_what = "Failed to construct TSIGError object: " +
+            string(ex.what());
+        PyErr_SetString(PyExc_ValueError, ex_what.c_str());
+        return (-1);
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct TSIGError object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing TSIGError");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGError constructor");
+
+    return (-1);
+}
+
+void
+TSIGError_destroy(s_TSIGError* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGError_getCode(const s_TSIGError* const self) {
+    return (Py_BuildValue("I", self->cppobj->getCode()));
+}
+
+PyObject*
+TSIGError_toText(const s_TSIGError* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIGError object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIGError object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGError_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject*
+TSIGError_toRcode(const s_TSIGError* const self) {
+    typedef CPPPyObjectContainer<s_Rcode, Rcode> RcodePyObjectContainer;
+
+    try {
+        RcodePyObjectContainer rcode_container(PyObject_New(s_Rcode,
+                                                            &rcode_type));
+        rcode_container.set(new Rcode(self->cppobj->toRcode()));
+        return (rcode_container.release());
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIGError to Rcode: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIGError to Rcode");
+    }
+    return (NULL);
+}
+
+PyObject* 
+TSIGError_richcmp(const s_TSIGError* const self,
+                   const s_TSIGError* const other,
+                   const int op)
+{
+    bool c = false;
+
+    // Check for null and if the types match. If different type,
+    // simply return False
+    if (other == NULL || (self->ob_type != other->ob_type)) {
+        Py_RETURN_FALSE;
+    }
+
+    // Only equals and not equals here, unorderable type
+    switch (op) {
+    case Py_LT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_LE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_EQ:
+        c = (*self->cppobj == *other->cppobj);
+        break;
+    case Py_NE:
+        c = (*self->cppobj != *other->cppobj);
+        break;
+    case Py_GT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_GE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    }
+    if (c) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIGError
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigerror_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.TSIGError",
+    sizeof(s_TSIGError),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(TSIGError_destroy),       // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    TSIGError_str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    TSIGError_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    reinterpret_cast<richcmpfunc>(TSIGError_richcmp), // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIGError_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    reinterpret_cast<initproc>(TSIGError_init),            // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+namespace {
+// Trivial shortcut to create and install TSIGError constants.
+inline void
+installTSIGErrorConstant(const char* name, const TSIGError& val) {
+    TSIGErrorContainer container(PyObject_New(s_TSIGError, &tsigerror_type));
+    container.installAsClassVariable(tsigerror_type, name, new TSIGError(val));
+}
+}
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGError(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&tsigerror_type) < 0) {
+        return (false);
+    }
+    void* p = &tsigerror_type;
+    if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsigerror_type);
+
+    try {
+        // Constant class variables
+        // Error codes (bare values)
+        installClassVariable(tsigerror_type, "BAD_SIG_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_SIG_CODE));
+        installClassVariable(tsigerror_type, "BAD_KEY_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_KEY_CODE));
+        installClassVariable(tsigerror_type, "BAD_TIME_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_TIME_CODE));
+
+        // Error codes (constant objects)
+        installTSIGErrorConstant("NOERROR", TSIGError::NOERROR());
+        installTSIGErrorConstant("FORMERR", TSIGError::FORMERR());
+        installTSIGErrorConstant("SERVFAIL", TSIGError::SERVFAIL());
+        installTSIGErrorConstant("NXDOMAIN", TSIGError::NXDOMAIN());
+        installTSIGErrorConstant("NOTIMP", TSIGError::NOTIMP());
+        installTSIGErrorConstant("REFUSED", TSIGError::REFUSED());
+        installTSIGErrorConstant("YXDOMAIN", TSIGError::YXDOMAIN());
+        installTSIGErrorConstant("YXRRSET", TSIGError::YXRRSET());
+        installTSIGErrorConstant("NXRRSET", TSIGError::NXRRSET());
+        installTSIGErrorConstant("NOTAUTH", TSIGError::NOTAUTH());
+        installTSIGErrorConstant("NOTZONE", TSIGError::NOTZONE());
+        installTSIGErrorConstant("RESERVED11", TSIGError::RESERVED11());
+        installTSIGErrorConstant("RESERVED12", TSIGError::RESERVED12());
+        installTSIGErrorConstant("RESERVED13", TSIGError::RESERVED13());
+        installTSIGErrorConstant("RESERVED14", TSIGError::RESERVED14());
+        installTSIGErrorConstant("RESERVED15", TSIGError::RESERVED15());
+        installTSIGErrorConstant("BAD_SIG", TSIGError::BAD_SIG());
+        installTSIGErrorConstant("BAD_KEY", TSIGError::BAD_KEY());
+        installTSIGErrorConstant("BAD_TIME", TSIGError::BAD_TIME());
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in TSIGError initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in TSIGError initialization");
+        return (false);
+    }
+
+    return (true);
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/tsigerror_python.h b/src/lib/dns/python/tsigerror_python.h
new file mode 100644
index 0000000..f0c66cc
--- /dev/null
+++ b/src/lib/dns/python/tsigerror_python.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGERROR_H
+#define __PYTHON_TSIGERROR_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGError;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGError : public PyObject {
+public:
+    s_TSIGError();
+    const TSIGError* cppobj;
+};
+
+extern PyTypeObject tsigerror_type;
+
+bool initModulePart_TSIGError(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGERROR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tsigerror_python_inc.cc b/src/lib/dns/python/tsigerror_python_inc.cc
new file mode 100644
index 0000000..ed3b605
--- /dev/null
+++ b/src/lib/dns/python/tsigerror_python_inc.cc
@@ -0,0 +1,83 @@
+namespace {
+const char* const TSIGError_doc = "\n\
+TSIG errors.\n\
+\n\
+\n\
+The TSIGError class objects represent standard errors related to TSIG\n\
+protocol operations as defined in related specifications, mainly in\n\
+RFC2845.\n\
+\n\
+TSIGError(error_code)\n\
+\n\
+Constructor from the code value.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+Parameters:\n\
+  error_code: The underlying 16-bit error code value of the TSIGError.\n\
+\n\
+TSIGError(rcode)\n\
+\n\
+Constructor from Rcode.\n\
+\n\
+As defined in RFC2845, error code values from 0 to 15 (inclusive) are\n\
+derived from the DNS RCODEs, which are represented via the Rcode class\n\
+in this library. This constructor works as a converter from these\n\
+RCODEs to corresponding TSIGError objects.\n\
+\n\
+Exceptions:\n\
+  ValueError: Given rcode is not convertible to TSIGErrors.\n\
+\n\
+Parameters:\n\
+  rcode: the Rcode from which the TSIGError should be derived.\n\
+\n\
+";
+const char* const TSIGError_getCode_doc = "get_code() -> integer\n\
+\n\
+Returns the TSIGCode error code value.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+Return Value(s):\n\
+  The underlying code value corresponding to the TSIGError.\n\
+";
+const char* const TSIGError_toText_doc = "to_text() -> string\n\
+\n\
+Convert the TSIGError to a string.\n\
+\n\
+For codes derived from RCODEs up to 15, this method returns the same\n\
+string as Rcode.to_text() for the corresponding code. For other pre-\n\
+defined code values (see TSIGError.CodeValue), this method returns a\n\
+string representation of the \"mnemonic' used for the enum and\n\
+constant objects as defined in RFC2845. For example, the string for\n\
+code value 16 is \"BADSIG\", etc. For other code values it returns a\n\
+string representation of the decimal number of the value, e.g. \"32\",\n\
+\"100\", etc.\n\
+\n\
+Exceptions:\n\
+  None\n\
+\n\
+Return Value(s):\n\
+  A string representation of the TSIGError.\n\
+";
+const char* const TSIGError_toRcode_doc = "to_rcode() -> Rcode\n\
+\n\
+Convert the TSIGError to a Rcode.\n\
+\n\
+This method returns an Rcode object that is corresponding to the TSIG\n\
+error. The returned Rcode is expected to be used by a verifying server\n\
+to specify the RCODE of a response when TSIG verification fails.\n\
+\n\
+Specifically, this method returns Rcode.NOTAUTH() for the TSIG\n\
+specific errors, BADSIG, BADKEY, BADTIME, as described in RFC2845. For\n\
+errors derived from the standard Rcode (code 0-15), it returns the\n\
+corresponding Rcode. For others, this method returns Rcode.SERVFAIL()\n\
+as a last resort.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+";
+}
diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc
index ee2a2e4..bca8944 100644
--- a/src/lib/dns/python/tsigkey_python.cc
+++ b/src/lib/dns/python/tsigkey_python.cc
@@ -429,10 +429,12 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
 PyObject*
 TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
     s_Name* key_name;
+    s_Name* algorithm_name;
 
-    if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
+    if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
+                         &name_type, &algorithm_name)) {
         const TSIGKeyRing::FindResult result =
-            self->keyring->find(*key_name->name);
+            self->keyring->find(*key_name->name, *algorithm_name->name);
         if (result.key != NULL) {
             s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
             if (key == NULL) {
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 8a83aae..cb1bb1c 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -40,6 +40,10 @@ BUILT_SOURCES += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
 BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
 BUILT_SOURCES += rdata_tsig_toWire5.wire
 BUILT_SOURCES += tsigrecord_toWire1.wire tsigrecord_toWire2.wire
+BUILT_SOURCES += tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire
+BUILT_SOURCES += tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire
+BUILT_SOURCES += tsig_verify7.wire tsig_verify8.wire tsig_verify9.wire
+BUILT_SOURCES += tsig_verify10.wire
 
 # NOTE: keep this in sync with real file listing
 # so is included in tarball
@@ -108,6 +112,10 @@ EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
 EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
 EXTRA_DIST += rdata_tsig_toWire5.spec
 EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec
+EXTRA_DIST += tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec
+EXTRA_DIST += tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec
+EXTRA_DIST += tsig_verify7.spec tsig_verify8.spec tsig_verify9.spec
+EXTRA_DIST += tsig_verify10.spec
 
 .spec.wire:
 	./gen-wiredata.py -o $@ $<
diff --git a/src/lib/dns/tests/testdata/gen-wiredata.py.in b/src/lib/dns/tests/testdata/gen-wiredata.py.in
index 7a82bfd..fd98c6e 100755
--- a/src/lib/dns/tests/testdata/gen-wiredata.py.in
+++ b/src/lib/dns/tests/testdata/gen-wiredata.py.in
@@ -283,9 +283,8 @@ class NS(RR):
         f.write('# NS name=%s\n' % (self.nsname))
         f.write('%s\n' % nsname_wire)
 
-class SOA:
-    # this currently doesn't support name compression within the RDATA.
-    rdlen = -1                  # auto-calculate
+class SOA(RR):
+    rdlen = None                  # auto-calculate
     mname = 'ns.example.com'
     rname = 'root.example.com'
     serial = 2010012601
@@ -296,11 +295,9 @@ class SOA:
     def dump(self, f):
         mname_wire = encode_name(self.mname)
         rname_wire = encode_name(self.rname)
-        rdlen = self.rdlen
-        if rdlen < 0:
-            rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
-        f.write('\n# SOA RDATA (RDLEN=%d)\n' % rdlen)
-        f.write('%04x\n' % rdlen);
+        if self.rdlen is None:
+            self.rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
+        self.dump_header(f, self.rdlen)
         f.write('# NNAME=%s RNAME=%s\n' % (self.mname, self.rname))
         f.write('%s %s\n' % (mname_wire, rname_wire))
         f.write('# SERIAL(%d) REFRESH(%d) RETRY(%d) EXPIRE(%d) MINIMUM(%d)\n' %
diff --git a/src/lib/dns/tests/testdata/tsig_verify1.spec b/src/lib/dns/tests/testdata/tsig_verify1.spec
new file mode 100644
index 0000000..687013a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify1.spec
@@ -0,0 +1,19 @@
+#
+# An example of signed AXFR request
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x3410
+arcount: 1
+[question]
+rrtype: AXFR
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0x35b2fd08268781634400c7c8a5533b13
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify10.spec b/src/lib/dns/tests/testdata/tsig_verify10.spec
new file mode 100644
index 0000000..33ce83e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify10.spec
@@ -0,0 +1,22 @@
+#
+# A simple DNS query message with TSIG signed whose MAC is too short
+# (only 1 byte)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 1
+mac: 0x22
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify2.spec b/src/lib/dns/tests/testdata/tsig_verify2.spec
new file mode 100644
index 0000000..ff98ca3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify2.spec
@@ -0,0 +1,32 @@
+#
+# An example of signed AXFR response
+#
+
+[custom]
+sections: header:question:soa:tsig
+[header]
+id: 0x3410
+aa: 1
+qr: 1
+ancount: 1
+arcount: 1
+[question]
+rrtype: AXFR
+[soa]
+# note that names are compressed in this RR
+as_rr: True
+rr_name: ptr=12
+mname: ns.ptr=12
+rname: root.ptr=12
+serial: 2011041503
+refresh: 7200
+retry: 3600
+expire: 2592000
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0xbdd612cd2c7f9e0648bd6dc23713e83c
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify3.spec b/src/lib/dns/tests/testdata/tsig_verify3.spec
new file mode 100644
index 0000000..7e2f797
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify3.spec
@@ -0,0 +1,26 @@
+#
+# An example of signed AXFR response (continued)
+#
+
+[custom]
+sections: header:ns:tsig
+[header]
+id: 0x3410
+aa: 1
+qr: 1
+qdcount: 0
+ancount: 1
+arcount: 1
+[ns]
+# note that names are compressed in this RR
+as_rr: True
+rr_name: example.com.
+nsname: ns.ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8e951
+mac_size: 16
+mac: 0x102458f7f62ddd7d638d746034130968
+original_id: 0x3410
diff --git a/src/lib/dns/tests/testdata/tsig_verify4.spec b/src/lib/dns/tests/testdata/tsig_verify4.spec
new file mode 100644
index 0000000..4ffbbcf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify4.spec
@@ -0,0 +1,27 @@
+#
+# An example of signed DNS response with bogus MAC
+#
+
+[custom]
+sections: header:question:a:tsig
+[header]
+id: 0x2d65
+aa: 1
+qr: 1
+rd: 1
+ancount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a]
+as_rr: True
+rr_name: ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+# bogus MAC
+mac: 0xdeadbeefdeadbeefdeadbeefdeadbeef
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify5.spec b/src/lib/dns/tests/testdata/tsig_verify5.spec
new file mode 100644
index 0000000..a6cc643
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify5.spec
@@ -0,0 +1,26 @@
+#
+# An example of signed DNS response
+#
+
+[custom]
+sections: header:question:a:tsig
+[header]
+id: 0x2d65
+aa: 1
+qr: 1
+rd: 1
+ancount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a]
+as_rr: True
+rr_name: ptr=12
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x8fcda66a7cd1a3b9948eb1869d384a9f
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify6.spec b/src/lib/dns/tests/testdata/tsig_verify6.spec
new file mode 100644
index 0000000..32e0818
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify6.spec
@@ -0,0 +1,21 @@
+#
+# Forwarded DNS query message with TSIG signed (header ID != orig ID)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x1035
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify7.spec b/src/lib/dns/tests/testdata/tsig_verify7.spec
new file mode 100644
index 0000000..377578e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify7.spec
@@ -0,0 +1,21 @@
+#
+# DNS query message with TSIG that has empty MAC (invalidly)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 0
+mac: ''
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify8.spec b/src/lib/dns/tests/testdata/tsig_verify8.spec
new file mode 100644
index 0000000..5432d4a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify8.spec
@@ -0,0 +1,23 @@
+#
+# DNS query message with TSIG that has empty MAC + BADKEY error
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+# TSIG QNAME won't be compressed
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 0
+mac: ''
+# 17: BADKEY
+error: 17
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/tsig_verify9.spec b/src/lib/dns/tests/testdata/tsig_verify9.spec
new file mode 100644
index 0000000..5888455
--- /dev/null
+++ b/src/lib/dns/tests/testdata/tsig_verify9.spec
@@ -0,0 +1,21 @@
+#
+# A simple DNS query message with TSIG signed, but TSIG key and algorithm
+# names have upper case characters (unusual)
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: WWW.EXAMPLE.COM
+algorithm: HMAC-MD5.SIG-ALG.REG.INT
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
index efa0490..55c3ac2 100644
--- a/src/lib/dns/tests/tsig_unittest.cc
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -70,8 +70,13 @@ class TSIGTest : public ::testing::Test {
 protected:
     TSIGTest() :
         tsig_ctx(NULL), qid(0x2d65), test_name("www.example.com"),
-        test_class(RRClass::IN()), test_ttl(86400), message(Message::RENDER),
-        buffer(0), renderer(buffer)
+        badkey_name("badkey.example.com"), test_class(RRClass::IN()),
+        test_ttl(86400), message(Message::RENDER), buffer(0), renderer(buffer),
+        dummy_data(1024, 0xdd),  // should be sufficiently large for all tests
+        dummy_record(badkey_name, any::TSIG(TSIGKey::HMACMD5_NAME(),
+                                            0x4da8877a,
+                                            TSIGContext::DEFAULT_FUDGE,
+                                            0, NULL, qid, 0, 0, NULL))
     {
         // Make sure we use the system time by default so that we won't be
         // confused due to other tests that tweak the time.
@@ -103,6 +108,8 @@ protected:
                                             bool add_question = true,
                                             Rcode rcode = Rcode::NOERROR());
 
+    void createMessageFromFile(const char* datafile);
+
     // bit-wise constant flags to configure DNS header flags for test
     // messages.
     static const unsigned int QR_FLAG = 0x1;
@@ -111,14 +118,19 @@ protected:
 
     boost::scoped_ptr<TSIGContext> tsig_ctx;
     boost::scoped_ptr<TSIGContext> tsig_verify_ctx;
+    TSIGKeyRing keyring;
     const uint16_t qid;
     const Name test_name;
+    const Name badkey_name;
     const RRClass test_class;
     const RRTTL test_ttl;
     Message message;
     OutputBuffer buffer;
     MessageRenderer renderer;
     vector<uint8_t> secret;
+    vector<uint8_t> dummy_data;
+    const TSIGRecord dummy_record;
+    vector<uint8_t> received_data;
 };
 
 ConstTSIGRecordPtr
@@ -157,15 +169,27 @@ TSIGTest::createMessageAndSign(uint16_t id, const Name& qname,
     renderer.clear();
     message.toWire(renderer);
 
+    TSIGContext::State expected_new_state =
+        (ctx->getState() == TSIGContext::INIT) ?
+        TSIGContext::SENT_REQUEST : TSIGContext::SENT_RESPONSE;
     ConstTSIGRecordPtr tsig = ctx->sign(id, renderer.getData(),
                                         renderer.getLength());
-    EXPECT_EQ(TSIGContext::SIGNED, ctx->getState());
+    EXPECT_EQ(expected_new_state, ctx->getState());
 
     return (tsig);
 }
 
 void
-commonTSIGChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
+TSIGTest::createMessageFromFile(const char* datafile) {
+    message.clear(Message::PARSE);
+    received_data.clear();
+    UnitTestUtil::readWireData(datafile, received_data);
+    InputBuffer buffer(&received_data[0], received_data.size());
+    message.fromWire(buffer);
+}
+
+void
+commonSignChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
                  uint64_t expected_timesigned,
                  const uint8_t* expected_mac, size_t expected_maclen,
                  uint16_t expected_error = 0,
@@ -191,6 +215,17 @@ commonTSIGChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
                         expected_otherdata, expected_otherlen);
 }
 
+void
+commonVerifyChecks(TSIGContext& ctx, const TSIGRecord* record,
+                   const void* data, size_t data_len, TSIGError expected_error,
+                   TSIGContext::State expected_new_state =
+                   TSIGContext::VERIFIED_RESPONSE)
+{
+    EXPECT_EQ(expected_error, ctx.verify(record, data, data_len));
+    EXPECT_EQ(expected_error, ctx.getError());
+    EXPECT_EQ(expected_new_state, ctx.getState());
+}
+
 TEST_F(TSIGTest, initialState) {
     // Until signing or verifying, the state should be INIT
     EXPECT_EQ(TSIGContext::INIT, tsig_ctx->getState());
@@ -199,6 +234,38 @@ TEST_F(TSIGTest, initialState) {
     EXPECT_EQ(TSIGError(Rcode::NOERROR()), tsig_ctx->getError());
 }
 
+TEST_F(TSIGTest, constructFromKeyRing) {
+    // Construct a TSIG context with an empty key ring.  Key shouldn't be
+    // found, and the BAD_KEY error should be recorded.
+    TSIGContext ctx1(test_name, TSIGKey::HMACMD5_NAME(), keyring);
+    EXPECT_EQ(TSIGContext::INIT, ctx1.getState());
+    EXPECT_EQ(TSIGError::BAD_KEY(), ctx1.getError());
+
+    // Add a matching key (we don't use the secret so leave it empty), and
+    // construct it again.  This time it should be constructed with a valid
+    // key.
+    keyring.add(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(), NULL, 0));
+    TSIGContext ctx2(test_name, TSIGKey::HMACMD5_NAME(), keyring);
+    EXPECT_EQ(TSIGContext::INIT, ctx2.getState());
+    EXPECT_EQ(TSIGError::NOERROR(), ctx2.getError());
+
+    // Similar to the first case except that the key ring isn't empty but
+    // it doesn't contain a matching key.
+    TSIGContext ctx3(test_name, TSIGKey::HMACSHA1_NAME(), keyring);
+    EXPECT_EQ(TSIGContext::INIT, ctx3.getState());
+    EXPECT_EQ(TSIGError::BAD_KEY(), ctx3.getError());
+
+    TSIGContext ctx4(Name("different-key.example"), TSIGKey::HMACMD5_NAME(),
+                     keyring);
+    EXPECT_EQ(TSIGContext::INIT, ctx4.getState());
+    EXPECT_EQ(TSIGError::BAD_KEY(), ctx4.getError());
+
+    // "Unknown" algorithm name will result in BADKEY, too.
+    TSIGContext ctx5(test_name, Name("unknown.algorithm"), keyring);
+    EXPECT_EQ(TSIGContext::INIT, ctx5.getState());
+    EXPECT_EQ(TSIGError::BAD_KEY(), ctx5.getError());
+}
+
 // Example output generated by
 // "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com
 // QID: 0x2d65
@@ -213,7 +280,7 @@ TEST_F(TSIGTest, sign) {
 
     {
         SCOPED_TRACE("Sign test for query");
-        commonTSIGChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()),
+        commonSignChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()),
                          qid, 0x4da8877a, common_expected_mac,
                          sizeof(common_expected_mac));
     }
@@ -231,7 +298,7 @@ TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
 
     {
         SCOPED_TRACE("Sign test for query using non canonical key name");
-        commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+        commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
                          0x4da8877a, common_expected_mac,
                          sizeof(common_expected_mac));
     }
@@ -247,7 +314,7 @@ TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) {
 
     {
         SCOPED_TRACE("Sign test for query using non canonical algorithm name");
-        commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+        commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
                          0x4da8877a, common_expected_mac,
                          sizeof(common_expected_mac));
     }
@@ -281,6 +348,19 @@ TEST_F(TSIGTest, signBadData) {
     EXPECT_THROW(tsig_ctx->sign(0, &dummy_data, 0), InvalidParameter);
 }
 
+TEST_F(TSIGTest, verifyBadData) {
+    // the data must at least hold the DNS message header and the specified
+    // TSIG.
+    EXPECT_THROW(tsig_ctx->verify(&dummy_record, &dummy_data[0],
+                                  12 + dummy_record.getLength() - 1),
+                 InvalidParameter);
+
+    // And the data must not be NULL.
+    EXPECT_THROW(tsig_ctx->verify(&dummy_record, NULL,
+                                  12 + dummy_record.getLength()),
+                 InvalidParameter);
+}
+
 #ifdef ENABLE_CUSTOM_OPERATOR_NEW
 // We enable this test only when we enable custom new/delete at build time
 // We could enable/disable the test runtime using the gtest filter, but
@@ -293,11 +373,10 @@ TEST_F(TSIGTest, signExceptionSafety) {
     // complicated and involves more memory allocation, so the test result
     // won't be reliable.
 
-    tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
-                                                          tsig_ctx.get()),
-                                     TSIGError::BAD_KEY());
-    // At this point the state should be changed to "CHECKED"
-    ASSERT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+    commonVerifyChecks(*tsig_verify_ctx, &dummy_record, &dummy_data[0],
+                       dummy_data.size(), TSIGError::BAD_KEY(),
+                       TSIGContext::RECEIVED_REQUEST);
+
     try {
         int dummydata;
         isc::util::unittests::force_throw_on_new = true;
@@ -308,8 +387,8 @@ TEST_F(TSIGTest, signExceptionSafety) {
     } catch (const std::bad_alloc&) {
         isc::util::unittests::force_throw_on_new = false;
 
-        // sign() threw, so the state should still be "CHECKED".
-        EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+        // sign() threw, so the state should still be RECEIVED_REQUEST
+        EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState());
     }
     isc::util::unittests::force_throw_on_new = false;
 }
@@ -339,70 +418,130 @@ TEST_F(TSIGTest, signUsingHMACSHA1) {
     };
     {
         SCOPED_TRACE("Sign test using HMAC-SHA1");
-        commonTSIGChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx),
+        commonSignChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx),
                          sha1_qid, 0x4dae7d5f, expected_mac,
                          sizeof(expected_mac), 0, 0, NULL,
                          TSIGKey::HMACSHA1_NAME());
     }
 }
 
-// An example response to the signed query used for the "sign" test.
+// The first part of this test checks verifying the signed query used for
+// the "sign" test.
+// The second part of this test generates a signed response to the signed
+// query as follows:
 // Answer: www.example.com. 86400 IN A 192.0.2.1
 // MAC: 8fcda66a7cd1a3b9948eb1869d384a9f
-TEST_F(TSIGTest, signResponse) {
+TEST_F(TSIGTest, verifyThenSignResponse) {
     isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
-    ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
-                                                   tsig_ctx.get());
-    tsig_verify_ctx->verifyTentative(tsig);
-    EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+    // This test data for the message test has the same wire format data
+    // as the message used in the "sign" test.
+    createMessageFromFile("message_toWire2.wire");
+    {
+        SCOPED_TRACE("Verify test for request");
+        commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+    }
 
     // Transform the original message to a response, then sign the response
     // with the context of "verified state".
-    tsig = createMessageAndSign(qid, test_name, tsig_verify_ctx.get(),
-                                QR_FLAG|AA_FLAG|RD_FLAG,
-                                RRType::A(), "192.0.2.1");
+    ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+                                                   tsig_verify_ctx.get(),
+                                                   QR_FLAG|AA_FLAG|RD_FLAG,
+                                                   RRType::A(), "192.0.2.1");
     const uint8_t expected_mac[] = {
         0x8f, 0xcd, 0xa6, 0x6a, 0x7c, 0xd1, 0xa3, 0xb9,
         0x94, 0x8e, 0xb1, 0x86, 0x9d, 0x38, 0x4a, 0x9f
     };
     {
         SCOPED_TRACE("Sign test for response");
-        commonTSIGChecks(tsig, qid, 0x4da8877a,
-                         expected_mac, sizeof(expected_mac));
+        commonSignChecks(tsig, qid, 0x4da8877a, expected_mac,
+                         sizeof(expected_mac));
+    }
+}
+
+TEST_F(TSIGTest, verifyUpperCaseNames) {
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    // This test data for the message test has the same wire format data
+    // as the message used in the "sign" test.
+    createMessageFromFile("tsig_verify9.wire");
+    {
+        SCOPED_TRACE("Verify test for request");
+        commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
+    }
+}
+
+TEST_F(TSIGTest, verifyForwardedMessage) {
+    // Similar to the first part of the previous test, but this test emulates
+    // the "forward" case, where the ID of the Header and the original ID in
+    // TSIG is different.
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    createMessageFromFile("tsig_verify6.wire");
+    {
+        SCOPED_TRACE("Verify test for forwarded request");
+        commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST);
     }
 }
 
 // Example of signing multiple messages in a single TCP stream,
 // taken from data using BIND 9's "one-answer" transfer-format.
+// Request:
+//   QID: 0x3410, flags (none)
+//   Question: example.com/IN/AXFR
+//   Time Signed: 0x4da8e951
+//   MAC: 35b2fd08268781634400c7c8a5533b13
 // First message:
 //   QID: 0x3410, flags QR, AA
 //   Question: example.com/IN/AXFR
 //   Answer: example.com. 86400 IN SOA ns.example.com. root.example.com. (
 //                          2011041503 7200 3600 2592000 1200)
-//   Time Signed: 0x4da8e951
+//   MAC: bdd612cd2c7f9e0648bd6dc23713e83c
 // Second message:
-//    Answer: example.com. 86400 IN NS ns.example.com.
-//    MAC: 102458f7f62ddd7d638d746034130968
+//   Answer: example.com. 86400 IN NS ns.example.com.
+//   MAC: 102458f7f62ddd7d638d746034130968
 TEST_F(TSIGTest, signContinuation) {
     isc::util::detail::gettimeFunction = testGetTime<0x4da8e951>;
 
     const uint16_t axfr_qid = 0x3410;
     const Name zone_name("example.com");
 
-    // Create and sign the AXFR request, then verify it.
-    tsig_verify_ctx->verifyTentative(createMessageAndSign(axfr_qid, zone_name,
-                                                          tsig_ctx.get(), 0,
-                                                          RRType::AXFR()));
-    EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+    // Create and sign the AXFR request
+    ConstTSIGRecordPtr tsig = createMessageAndSign(axfr_qid, zone_name,
+                                                   tsig_ctx.get(), 0,
+                                                   RRType::AXFR());
+    // Then verify it (the wire format test data should contain the same
+    // message data, and verification should succeed).
+    received_data.clear();
+    UnitTestUtil::readWireData("tsig_verify1.wire", received_data);
+    {
+        SCOPED_TRACE("Verify AXFR query");
+        commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &received_data[0],
+                           received_data.size(), TSIGError::NOERROR(),
+                           TSIGContext::RECEIVED_REQUEST);
+    }
 
-    // Create and sign the first response message (we don't need the result
-    // for the purpose of this test)
-    createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
-                         AA_FLAG|QR_FLAG, RRType::AXFR(),
-                         "ns.example.com. root.example.com. "
-                         "2011041503 7200 3600 2592000 1200",
-                         &RRType::SOA());
+    // Create and sign the first response message
+    tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
+                                AA_FLAG|QR_FLAG, RRType::AXFR(),
+                                "ns.example.com. root.example.com. "
+                                "2011041503 7200 3600 2592000 1200",
+                                &RRType::SOA());
+
+    // Then verify it at the requester side.
+    received_data.clear();
+    UnitTestUtil::readWireData("tsig_verify2.wire", received_data);
+    {
+        SCOPED_TRACE("Verify first AXFR response");
+        commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0],
+                           received_data.size(), TSIGError::NOERROR());
+    }
 
     // Create and sign the second response message
     const uint8_t expected_mac[] = {
@@ -411,13 +550,20 @@ TEST_F(TSIGTest, signContinuation) {
     };
     {
         SCOPED_TRACE("Sign test for continued response in TCP stream");
-        commonTSIGChecks(createMessageAndSign(axfr_qid, zone_name,
-                                              tsig_verify_ctx.get(),
-                                              AA_FLAG|QR_FLAG, RRType::AXFR(),
-                                              "ns.example.com.", &RRType::NS(),
-                                              false),
-                         axfr_qid, 0x4da8e951,
-                         expected_mac, sizeof(expected_mac));
+        tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
+                                    AA_FLAG|QR_FLAG, RRType::AXFR(),
+                                    "ns.example.com.", &RRType::NS(), false);
+        commonSignChecks(tsig, axfr_qid, 0x4da8e951, expected_mac,
+                         sizeof(expected_mac));
+    }
+
+    // Then verify it at the requester side.
+    received_data.clear();
+    UnitTestUtil::readWireData("tsig_verify3.wire", received_data);
+    {
+        SCOPED_TRACE("Verify second AXFR response");
+        commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0],
+                           received_data.size(), TSIGError::NOERROR());
     }
 }
 
@@ -443,10 +589,13 @@ TEST_F(TSIGTest, badtimeResponse) {
                                                    RRType::SOA());
 
     // "advance the clock" and try validating, which should fail due to BADTIME
-    // (verifyTentative actually doesn't check the time, though)
     isc::util::detail::gettimeFunction = testGetTime<0x4da8be86>;
-    tsig_verify_ctx->verifyTentative(tsig, TSIGError::BAD_TIME());
-    EXPECT_EQ(TSIGError::BAD_TIME(), tsig_verify_ctx->getError());
+    {
+        SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG");
+        commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0],
+                           dummy_data.size(), TSIGError::BAD_TIME(),
+                           TSIGContext::RECEIVED_REQUEST);
+    }
 
     // make and sign a response in the context of TSIG error.
     tsig = createMessageAndSign(test_qid, test_name, tsig_verify_ctx.get(),
@@ -459,7 +608,7 @@ TEST_F(TSIGTest, badtimeResponse) {
     };
     {
         SCOPED_TRACE("Sign test for response with BADTIME");
-        commonTSIGChecks(tsig, message.getQid(), 0x4da8b9d6,
+        commonSignChecks(tsig, message.getQid(), 0x4da8b9d6,
                          expected_mac, sizeof(expected_mac),
                          18,     // error: BADTIME
                          sizeof(expected_otherdata),
@@ -467,21 +616,86 @@ TEST_F(TSIGTest, badtimeResponse) {
     }
 }
 
+TEST_F(TSIGTest, badtimeResponse2) {
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
+
+    ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+                                                   tsig_ctx.get(), 0,
+                                                   RRType::SOA());
+
+    // "rewind the clock" and try validating, which should fail due to BADTIME
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 - 600>;
+    {
+        SCOPED_TRACE("Verify resulting in BADTIME due to too future SIG");
+        commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0],
+                           dummy_data.size(), TSIGError::BAD_TIME(),
+                           TSIGContext::RECEIVED_REQUEST);
+    }
+}
+
+TEST_F(TSIGTest, badtimeBoundaries) {
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
+
+    // Test various boundary conditions.  We intentionally use the magic
+    // number of 300 instead of the constant variable for testing.
+    // In the okay cases, signature is not correct, but it's sufficient to
+    // check the error code isn't BADTIME for the purpose of this test.
+    ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+                                                   tsig_ctx.get(), 0,
+                                                   RRType::SOA());
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 + 301>;
+    EXPECT_EQ(TSIGError::BAD_TIME(),
+              tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+                                      dummy_data.size()));
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 + 300>;
+    EXPECT_NE(TSIGError::BAD_TIME(),
+              tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+                                      dummy_data.size()));
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 - 301>;
+    EXPECT_EQ(TSIGError::BAD_TIME(),
+              tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+                                      dummy_data.size()));
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8b9d6 - 300>;
+    EXPECT_NE(TSIGError::BAD_TIME(),
+              tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+                                      dummy_data.size()));
+}
+
+TEST_F(TSIGTest, badtimeOverflow) {
+    isc::util::detail::gettimeFunction = testGetTime<200>;
+    ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+                                                   tsig_ctx.get(), 0,
+                                                   RRType::SOA());
+
+    // This should be in the okay range, but since "200 - fudge" overflows
+    // and we compare them as 64-bit unsigned integers, it results in a false
+    // positive (we intentionally accept that).
+    isc::util::detail::gettimeFunction = testGetTime<100>;
+    EXPECT_EQ(TSIGError::BAD_TIME(),
+              tsig_verify_ctx->verify(tsig.get(), &dummy_data[0],
+                                      dummy_data.size()));
+}
+
 TEST_F(TSIGTest, badsigResponse) {
     isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
 
-    // Sign a simple message, and force the verification to fail with
-    // BADSIG.
-    tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
-                                                          tsig_ctx.get()),
-                                     TSIGError::BAD_SIG());
+    // Try to sign a simple message with bogus secret.  It should fail
+    // with BADSIG.
+    createMessageFromFile("message_toWire2.wire");
+    TSIGContext bad_ctx(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(),
+                                &dummy_data[0], dummy_data.size()));
+    {
+        SCOPED_TRACE("Verify resulting in BADSIG");
+        commonVerifyChecks(bad_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
+    }
 
     // Sign the same message (which doesn't matter for this test) with the
     // context of "checked state".
     {
         SCOPED_TRACE("Sign test for response with BADSIG error");
-        commonTSIGChecks(createMessageAndSign(qid, test_name,
-                                              tsig_verify_ctx.get()),
+        commonSignChecks(createMessageAndSign(qid, test_name, &bad_ctx),
                          message.getQid(), 0x4da8877a, NULL, 0,
                          16);   // 16: BADSIG
     }
@@ -490,15 +704,203 @@ TEST_F(TSIGTest, badsigResponse) {
 TEST_F(TSIGTest, badkeyResponse) {
     // A similar test as badsigResponse but for BADKEY
     isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
-    tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
-                                                          tsig_ctx.get()),
-                                     TSIGError::BAD_KEY());
+    tsig_ctx.reset(new TSIGContext(badkey_name, TSIGKey::HMACMD5_NAME(),
+                                   keyring));
+    {
+        SCOPED_TRACE("Verify resulting in BADKEY");
+        commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0],
+                           dummy_data.size(), TSIGError::BAD_KEY(),
+                           TSIGContext::RECEIVED_REQUEST);
+    }
+
     {
         SCOPED_TRACE("Sign test for response with BADKEY error");
-        commonTSIGChecks(createMessageAndSign(qid, test_name,
-                                              tsig_verify_ctx.get()),
-                         message.getQid(), 0x4da8877a, NULL, 0,
-                         17);   // 17: BADKEYSIG
+        ConstTSIGRecordPtr sig = createMessageAndSign(qid, test_name,
+                                                      tsig_ctx.get());
+        EXPECT_EQ(badkey_name, sig->getName());
+        commonSignChecks(sig, qid, 0x4da8877a, NULL, 0, 17);   // 17: BADKEY
+    }
+}
+
+TEST_F(TSIGTest, badkeyForResponse) {
+    // "BADKEY" case for a response to a signed message
+    createMessageAndSign(qid, test_name, tsig_ctx.get());
+    {
+        SCOPED_TRACE("Verify a response resulting in BADKEY");
+        commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0],
+                           dummy_data.size(), TSIGError::BAD_KEY(),
+                           TSIGContext::SENT_REQUEST);
+    }
+
+    // A similar case with a different algorithm
+    const TSIGRecord dummy_record2(test_name,
+                                  any::TSIG(TSIGKey::HMACSHA1_NAME(),
+                                            0x4da8877a,
+                                            TSIGContext::DEFAULT_FUDGE,
+                                            0, NULL, qid, 0, 0, NULL));
+    {
+        SCOPED_TRACE("Verify a response resulting in BADKEY due to bad alg");
+        commonVerifyChecks(*tsig_ctx, &dummy_record2, &dummy_data[0],
+                           dummy_data.size(), TSIGError::BAD_KEY(),
+                           TSIGContext::SENT_REQUEST);
+    }
+}
+
+TEST_F(TSIGTest, badsigThenValidate) {
+    // According to RFC2845 4.6, if TSIG verification fails the client
+    // should discard that message and wait for another signed response.
+    // This test emulates that situation.
+
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    createMessageAndSign(qid, test_name, tsig_ctx.get());
+
+    createMessageFromFile("tsig_verify4.wire");
+    {
+        SCOPED_TRACE("Verify a response that should fail due to BADSIG");
+        commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::BAD_SIG(), TSIGContext::SENT_REQUEST);
+    }
+
+    createMessageFromFile("tsig_verify5.wire");
+    {
+        SCOPED_TRACE("Verify a response after a BADSIG failure");
+        commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::NOERROR(),
+                           TSIGContext::VERIFIED_RESPONSE);
+    }
+}
+
+TEST_F(TSIGTest, nosigThenValidate) {
+    // Similar to the previous test, but the first response doesn't contain
+    // TSIG.
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    createMessageAndSign(qid, test_name, tsig_ctx.get());
+
+    {
+        SCOPED_TRACE("Verify a response without TSIG that should exist");
+        commonVerifyChecks(*tsig_ctx, NULL, &dummy_data[0],
+                           dummy_data.size(), TSIGError::FORMERR(),
+                           TSIGContext::SENT_REQUEST);
+    }
+
+    createMessageFromFile("tsig_verify5.wire");
+    {
+        SCOPED_TRACE("Verify a response after a FORMERR failure");
+        commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::NOERROR(),
+                           TSIGContext::VERIFIED_RESPONSE);
+    }
+}
+
+TEST_F(TSIGTest, badtimeThenValidate) {
+    // Similar to the previous test, but the first response results in BADTIME.
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+                                                   tsig_ctx.get());
+
+    // "advance the clock" and try validating, which should fail due to BADTIME
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a + 600>;
+    {
+        SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG");
+        commonVerifyChecks(*tsig_ctx, tsig.get(), &dummy_data[0],
+                           dummy_data.size(), TSIGError::BAD_TIME(),
+                           TSIGContext::SENT_REQUEST);
+    }
+
+    // revert the clock again.
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    createMessageFromFile("tsig_verify5.wire");
+    {
+        SCOPED_TRACE("Verify a response after a BADTIME failure");
+        commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::NOERROR(),
+                           TSIGContext::VERIFIED_RESPONSE);
+    }
+}
+
+TEST_F(TSIGTest, emptyMAC) {
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    // We don't allow empty MAC unless the TSIG error is BADSIG or BADKEY.
+    createMessageFromFile("tsig_verify7.wire");
+    {
+        SCOPED_TRACE("Verify test for request");
+        commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
+    }
+
+    // If the empty MAC comes with a BADKEY error, the error is passed
+    // transparently.
+    createMessageFromFile("tsig_verify8.wire");
+    {
+        SCOPED_TRACE("Verify test for request");
+        commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::BAD_KEY(), TSIGContext::RECEIVED_REQUEST);
+    }
+}
+
+TEST_F(TSIGTest, verifyAfterSendResponse) {
+    // Once the context is used for sending a signed response, it shouldn't
+    // be used for further verification.
+
+    // The following are essentially the same as what verifyThenSignResponse
+    // does with simplification.
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    createMessageFromFile("message_toWire2.wire");
+    tsig_verify_ctx->verify(message.getTSIGRecord(), &received_data[0],
+                            received_data.size());
+    EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState());
+    createMessageAndSign(qid, test_name, tsig_verify_ctx.get(),
+                         QR_FLAG|AA_FLAG|RD_FLAG, RRType::A(), "192.0.2.1");
+    EXPECT_EQ(TSIGContext::SENT_RESPONSE, tsig_verify_ctx->getState());
+
+    // Now trying further verification.
+    createMessageFromFile("message_toWire2.wire");
+    EXPECT_THROW(tsig_verify_ctx->verify(message.getTSIGRecord(),
+                                         &received_data[0],
+                                         received_data.size()),
+                 TSIGContextError);
+}
+
+TEST_F(TSIGTest, signAfterVerified) {
+    // Likewise, once the context verifies a response, it shouldn't for
+    // signing any more.
+
+    // The following are borrowed from badsigThenValidate (without the
+    // intermediate failure)
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    createMessageAndSign(qid, test_name, tsig_ctx.get());
+    createMessageFromFile("tsig_verify5.wire");
+    tsig_ctx->verify(message.getTSIGRecord(), &received_data[0],
+                     received_data.size());
+    EXPECT_EQ(TSIGContext::VERIFIED_RESPONSE, tsig_ctx->getState());
+
+    // Now trying further signing.
+    EXPECT_THROW(createMessageAndSign(qid, test_name, tsig_ctx.get()),
+                 TSIGContextError);
+}
+
+TEST_F(TSIGTest, tooShortMAC) {
+    // Too short MAC should be rejected.
+    // Note: when we implement RFC4635-based checks, the error code will
+    // (probably) be FORMERR.
+
+    isc::util::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    createMessageFromFile("tsig_verify10.wire");
+    {
+        SCOPED_TRACE("Verify test for request");
+        commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(),
+                           &received_data[0], received_data.size(),
+                           TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST);
     }
 }
 
diff --git a/src/lib/dns/tests/tsigerror_unittest.cc b/src/lib/dns/tests/tsigerror_unittest.cc
index 5866587..bb08aef 100644
--- a/src/lib/dns/tests/tsigerror_unittest.cc
+++ b/src/lib/dns/tests/tsigerror_unittest.cc
@@ -93,6 +93,20 @@ TEST(TSIGErrorTest, toText) {
     EXPECT_EQ("65535", TSIGError(65535).toText());
 }
 
+TEST(TSIGErrorTest, toRcode) {
+    // TSIGError derived from the standard Rcode
+    EXPECT_EQ(Rcode::NOERROR(), TSIGError(Rcode::NOERROR()).toRcode());
+
+    // Well known TSIG errors
+    EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_SIG().toRcode());
+    EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_KEY().toRcode());
+    EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_TIME().toRcode());
+
+    // Unknown (or not yet supported) codes are treated as SERVFAIL.
+    EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(19).toRcode());
+    EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(65535).toRcode());
+}
+
 // test operator<<.  We simply confirm it appends the result of toText().
 TEST(TSIGErrorTest, LeftShiftOperator) {
     ostringstream oss;
diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc
index da353d9..20ee802 100644
--- a/src/lib/dns/tests/tsigkey_unittest.cc
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -33,7 +33,7 @@ class TSIGKeyTest : public ::testing::Test {
 protected:
     TSIGKeyTest() : secret("someRandomData"), key_name("example.com") {}
     string secret;
-    Name key_name;
+    const Name key_name;
 };
 
 TEST_F(TSIGKeyTest, algorithmNames) {
@@ -71,9 +71,13 @@ TEST_F(TSIGKeyTest, construct) {
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret.c_str(),
                         secret.size(), key.getSecret(), key.getSecretLength());
 
+    // "unknown" algorithm is only accepted with empty secret.
     EXPECT_THROW(TSIGKey(key_name, Name("unknown-alg"),
                          secret.c_str(), secret.size()),
                  isc::InvalidParameter);
+    TSIGKey key2(key_name, Name("unknown-alg"), NULL, 0);
+    EXPECT_EQ(key_name, key2.getKeyName());
+    EXPECT_EQ(Name("unknown-alg"), key2.getAlgorithmName());
 
     // The algorithm name should be converted to the canonical form.
     EXPECT_EQ("hmac-sha1.",
@@ -81,6 +85,7 @@ TEST_F(TSIGKeyTest, construct) {
                       secret.c_str(),
                       secret.size()).getAlgorithmName().toText());
 
+    // Same for key name
     EXPECT_EQ("example.com.",
               TSIGKey(Name("EXAMPLE.CoM."), TSIGKey::HMACSHA256_NAME(),
                       secret.c_str(),
@@ -137,12 +142,18 @@ class TSIGKeyRingTest : public ::testing::Test {
 protected:
     TSIGKeyRingTest() :
         key_name("example.com"),
+        md5_name("hmac-md5.sig-alg.reg.int"),
+        sha1_name("hmac-sha1"),
+        sha256_name("hmac-sha256"),
         secretstring("anotherRandomData"),
         secret(secretstring.c_str()),
         secret_len(secretstring.size())
     {}
     TSIGKeyRing keyring;
-    Name key_name;
+    const Name key_name;
+    const Name md5_name;
+    const Name sha1_name;
+    const Name sha256_name;
 private:
     const string secretstring;
 protected:
@@ -162,8 +173,8 @@ TEST_F(TSIGKeyRingTest, add) {
     EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
                   TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
                           secret, secret_len)));
-    // keys are identified their names, the same name of key with a different
-    // algorithm would be considered a duplicate.
+    // keys are identified by their names, the same name of key with a
+    // different algorithm would be considered a duplicate.
     EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
                   TSIGKey(Name("example.com"), TSIGKey::HMACSHA1_NAME(),
                           secret, secret_len)));
@@ -215,44 +226,60 @@ TEST_F(TSIGKeyRingTest, removeFromSome) {
 }
 
 TEST_F(TSIGKeyRingTest, find) {
-    EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name).code);
-    EXPECT_EQ(static_cast<const TSIGKey*>(NULL), keyring.find(key_name).key);
-
-    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
-                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
-                          secret, secret_len)));
-    const TSIGKeyRing::FindResult result(keyring.find(key_name));
-    EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
-    EXPECT_EQ(key_name, result.key->getKeyName());
-    EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result.key->getAlgorithmName());
+    // If the keyring is empty the search should fail.
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name, md5_name).code);
+    EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
+              keyring.find(key_name, md5_name).key);
+
+    // Add a key and try to find it.  Should succeed.
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name,
+                                                        secret, secret_len)));
+    const TSIGKeyRing::FindResult result1(keyring.find(key_name, sha256_name));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, result1.code);
+    EXPECT_EQ(key_name, result1.key->getKeyName());
+    EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result1.key->getAlgorithmName());
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
-                        result.key->getSecret(),
-                        result.key->getSecretLength());
+                        result1.key->getSecret(),
+                        result1.key->getSecretLength());
+
+    // If either key name or algorithm doesn't match, search should fail.
+    const TSIGKeyRing::FindResult result2 =
+        keyring.find(Name("different-key.example"), sha256_name);
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND, result2.code);
+    EXPECT_EQ(static_cast<const TSIGKey*>(NULL), result2.key);
+
+    const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name);
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code);
+    EXPECT_EQ(static_cast<const TSIGKey*>(NULL), result3.key);
 }
 
 TEST_F(TSIGKeyRingTest, findFromSome) {
     // essentially the same test, but search a larger set
 
-    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
-                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
-                          secret, secret_len)));
-    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
-                  TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
-                          secret, secret_len)));
-    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
-                  TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
-                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name,
+                                                        secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("another.example"),
+                                                        md5_name,
+                                                        secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("more.example"),
+                                                        sha1_name,
+                                                        secret, secret_len)));
 
     const TSIGKeyRing::FindResult result(
-        keyring.find(Name("another.example")));
+        keyring.find(Name("another.example"), md5_name));
     EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
     EXPECT_EQ(Name("another.example"), result.key->getKeyName());
     EXPECT_EQ(TSIGKey::HMACMD5_NAME(), result.key->getAlgorithmName());
 
     EXPECT_EQ(TSIGKeyRing::NOTFOUND,
-              keyring.find(Name("noexist.example")).code);
+              keyring.find(Name("noexist.example"), sha1_name).code);
     EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
-              keyring.find(Name("noexist.example")).key);
+              keyring.find(Name("noexist.example"), sha256_name).key);
+
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND,
+              keyring.find(Name("another.example"), sha1_name).code);
+    EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
+              keyring.find(Name("another.example"), sha256_name).key);
 }
 
 TEST(TSIGStringTest, TSIGKeyFromToString) {
@@ -260,6 +287,8 @@ TEST(TSIGStringTest, TSIGKeyFromToString) {
     TSIGKey k2 = TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.");
     TSIGKey k3 = TSIGKey("test.example:MSG6Ng==");
     TSIGKey k4 = TSIGKey(Name("test.example."), Name("hmac-sha1."), NULL, 0);
+    // "Unknown" key with empty secret is okay
+    TSIGKey k5 = TSIGKey("test.example.::unknown");
 
     EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
               k1.toText());
@@ -268,6 +297,8 @@ TEST(TSIGStringTest, TSIGKeyFromToString) {
     EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.",
               k3.toText());
     EXPECT_EQ("test.example.::hmac-sha1.", k4.toText());
+    EXPECT_EQ(Name("test.example."), k5.getKeyName());
+    EXPECT_EQ(Name("unknown"), k5.getAlgorithmName());
 
     EXPECT_THROW(TSIGKey(""), isc::InvalidParameter);
     EXPECT_THROW(TSIGKey(":"), isc::InvalidParameter);
@@ -278,7 +309,6 @@ TEST(TSIGStringTest, TSIGKeyFromToString) {
     EXPECT_THROW(TSIGKey("test.example.:"), isc::InvalidParameter);
     EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:"), isc::InvalidParameter);
     EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:unknown"), isc::InvalidParameter);
-
 }
 
 
diff --git a/src/lib/dns/tests/tsigrecord_unittest.cc b/src/lib/dns/tests/tsigrecord_unittest.cc
index a932b7f..e1b3e93 100644
--- a/src/lib/dns/tests/tsigrecord_unittest.cc
+++ b/src/lib/dns/tests/tsigrecord_unittest.cc
@@ -98,12 +98,11 @@ TEST_F(TSIGRecordTest, fromParams) {
 
     // Unexpected class
     EXPECT_THROW(TSIGRecord(test_name, RRClass::IN(), TSIGRecord::getTTL(),
-                            test_rdata, 85),
-                 DNSMessageFORMERR);
+                            test_rdata, 85), DNSMessageFORMERR);
 
-    // Unexpected TTL (simply ignored)
-    EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
-                               RRTTL(3600), test_rdata, 85));
+    // Unexpected TTL
+    EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+                            RRTTL(3600), test_rdata, 85), DNSMessageFORMERR);
 }
 
 TEST_F(TSIGRecordTest, recordToWire) {
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
index 8e8301d..714b2a5 100644
--- a/src/lib/dns/tsig.cc
+++ b/src/lib/dns/tsig.cc
@@ -16,7 +16,7 @@
 
 #include <stdint.h>
 
-#include <cassert>              // for the tentative verifyTentative()
+#include <cassert>
 #include <vector>
 
 #include <boost/shared_ptr.hpp>
@@ -44,6 +44,17 @@ namespace isc {
 namespace dns {
 namespace {
 typedef boost::shared_ptr<HMAC> HMACPtr;
+
+// TSIG uses 48-bit unsigned integer to represent time signed.
+// Since gettimeWrapper() returns a 64-bit *signed* integer, we
+// make sure it's stored in an unsigned 64-bit integer variable and
+// represents a value in the expected range.  (In reality, however,
+// gettimeWrapper() will return a positive integer that will fit
+// in 48 bits)
+uint64_t
+getTSIGTime() {
+    return (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
+}
 }
 
 struct TSIGContext::TSIGContextImpl {
@@ -51,17 +62,172 @@ struct TSIGContext::TSIGContextImpl {
         state_(INIT), key_(key), error_(Rcode::NOERROR()),
         previous_timesigned_(0)
     {}
+
+    // This helper method is used from verify().  It's expected to be called
+    // just before verify() returns.  It updates internal state based on
+    // the verification result and return the TSIGError to be returned to
+    // the caller of verify(), so that verify() can call this method within
+    // its 'return' statement.
+    TSIGError postVerifyUpdate(TSIGError error, const void* digest,
+                               uint16_t digest_len)
+    {
+        if (state_ == INIT) {
+            state_ = RECEIVED_REQUEST;
+        } else if (state_ == SENT_REQUEST && error == TSIGError::NOERROR()) {
+            state_ = VERIFIED_RESPONSE;
+        }
+        if (digest != NULL) {
+            previous_digest_.assign(static_cast<const uint8_t*>(digest),
+                                    static_cast<const uint8_t*>(digest) +
+                                    digest_len);
+        }
+        error_ = error;
+        return (error);
+    }
+
+    // The following three are helper methods to compute the digest for
+    // TSIG sign/verify in order to unify the common code logic for sign()
+    // and verify() and to keep these callers concise.
+    // These methods take an HMAC object, which will be updated with the
+    // calculated digest.
+    // Note: All methods construct a local OutputBuffer as a work space with a
+    // fixed initial buffer size to avoid intermediate buffer extension.
+    // This should be efficient enough, especially for fundamentally expensive
+    // operation like cryptographic sign/verify, but if the creation of the
+    // buffer in each helper method is still identified to be a severe
+    // performance bottleneck, we could have this class a buffer as a member
+    // variable and reuse it throughout the object's lifetime.  Right now,
+    // we prefer keeping the scope for local things as small as possible.
+    void digestPreviousMAC(HMACPtr hmac) const;
+    void digestTSIGVariables(HMACPtr hmac, uint16_t rrclass, uint32_t rrttl,
+                             uint64_t time_signed, uint16_t fudge,
+                             uint16_t error, uint16_t otherlen,
+                             const void* otherdata,
+                             bool time_variables_only) const;
+    void digestDNSMessage(HMACPtr hmac, uint16_t qid, const void* data,
+                          size_t data_len) const;
     State state_;
-    TSIGKey key_;
+    const TSIGKey key_;
     vector<uint8_t> previous_digest_;
     TSIGError error_;
     uint64_t previous_timesigned_; // only meaningful for response with BADTIME
 };
 
+void
+TSIGContext::TSIGContextImpl::digestPreviousMAC(HMACPtr hmac) const {
+    // We should have ensured the digest size fits 16 bits within this class
+    // implementation.
+    assert(previous_digest_.size() <= 0xffff);
+
+    OutputBuffer buffer(sizeof(uint16_t) + previous_digest_.size());
+    const uint16_t previous_digest_len(previous_digest_.size());
+    buffer.writeUint16(previous_digest_len);
+    if (previous_digest_len != 0) {
+        buffer.writeData(&previous_digest_[0], previous_digest_len);
+    }
+    hmac->update(buffer.getData(), buffer.getLength());
+}
+
+void
+TSIGContext::TSIGContextImpl::digestTSIGVariables(
+    HMACPtr hmac, uint16_t rrclass, uint32_t rrttl, uint64_t time_signed,
+    uint16_t fudge, uint16_t error, uint16_t otherlen, const void* otherdata,
+    bool time_variables_only) const
+{
+    // It's bit complicated, but we can still predict the necessary size of
+    // the data to be digested.  So we precompute it to avoid possible
+    // reallocation inside OutputBuffer (not absolutely necessary, but this
+    // is a bit more efficient)
+    size_t data_size = 8;
+    if (!time_variables_only) {
+        data_size += 10 + key_.getKeyName().getLength() +
+            key_.getAlgorithmName().getLength();
+    }
+    OutputBuffer buffer(data_size);
+
+    if (!time_variables_only) {
+        key_.getKeyName().toWire(buffer);
+        buffer.writeUint16(rrclass);
+        buffer.writeUint32(rrttl);
+        key_.getAlgorithmName().toWire(buffer);
+    }
+    buffer.writeUint16(time_signed >> 32);
+    buffer.writeUint32(time_signed & 0xffffffff);
+    buffer.writeUint16(fudge);
+
+    if (!time_variables_only) {
+        buffer.writeUint16(error);
+        buffer.writeUint16(otherlen);
+    }
+
+    hmac->update(buffer.getData(), buffer.getLength());
+    if (!time_variables_only && otherlen > 0) {
+        hmac->update(otherdata, otherlen);
+    }
+}
+
+// In digestDNSMessage, we exploit some minimum knowledge of DNS message
+// format:
+// - the header section has a fixed length of 12 octets (MESSAGE_HEADER_LEN)
+// - the offset in the header section to the ID field is 0
+// - the offset in the header section to the ARCOUNT field is 10 (and the field
+//   length is 2 octets)
+// We could construct a separate Message object from the given data, adjust
+// fields via the Message interfaces and then render it back to a separate
+// buffer, but that would be overkilling.  The DNS message header has a
+// fixed length and necessary modifications are quite straightforward, so
+// we do the job using lower level interfaces.
+namespace {
+const size_t MESSAGE_HEADER_LEN = 12;
+}
+void
+TSIGContext::TSIGContextImpl::digestDNSMessage(HMACPtr hmac,
+                                               uint16_t qid, const void* data,
+                                               size_t data_len) const
+{
+    OutputBuffer buffer(MESSAGE_HEADER_LEN);
+    const uint8_t* msgptr = static_cast<const uint8_t*>(data);
+
+    // Install the original ID
+    buffer.writeUint16(qid);
+    msgptr += sizeof(uint16_t);
+
+    // Copy the rest of the header except the ARCOUNT field.
+    buffer.writeData(msgptr, 8);
+    msgptr += 8;
+
+    // Install the adjusted ARCOUNT (we don't care even if the value is bogus
+    // and it underflows; it would simply result in verification failure)
+    buffer.writeUint16(InputBuffer(msgptr, sizeof(uint16_t)).readUint16() - 1);
+    msgptr += 2;
+
+    // Digest the header and the rest of the DNS message
+    hmac->update(buffer.getData(), buffer.getLength());
+    hmac->update(msgptr, data_len - MESSAGE_HEADER_LEN);
+}
+
 TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key))
 {
 }
 
+TSIGContext::TSIGContext(const Name& key_name, const Name& algorithm_name,
+                         const TSIGKeyRing& keyring) : impl_(NULL)
+{
+    const TSIGKeyRing::FindResult result(keyring.find(key_name,
+                                                      algorithm_name));
+    if (result.code == TSIGKeyRing::NOTFOUND) {
+        // If not key is found, create a dummy key with the specified key
+        // parameters and empty secret.  In the common scenario this will
+        // be used in subsequent response with a TSIG indicating a BADKEY
+        // error.
+        impl_ = new TSIGContextImpl(TSIGKey(key_name, algorithm_name,
+                                            NULL, 0));
+        impl_->error_ = TSIGError::BAD_KEY();
+    } else {
+        impl_ = new TSIGContextImpl(*result.key);
+    }
+}
+
 TSIGContext::~TSIGContext() {
     delete impl_;
 }
@@ -80,21 +246,20 @@ ConstTSIGRecordPtr
 TSIGContext::sign(const uint16_t qid, const void* const data,
                   const size_t data_len)
 {
+    if (impl_->state_ == VERIFIED_RESPONSE) {
+        isc_throw(TSIGContextError,
+                  "TSIG sign attempt after verifying a response");
+    }
+
     if (data == NULL || data_len == 0) {
         isc_throw(InvalidParameter, "TSIG sign error: empty data is given");
     }
 
     TSIGError error(TSIGError::NOERROR());
-    // TSIG uses 48-bit unsigned integer to represent time signed.
-    // Since gettimeofdayWrapper() returns a 64-bit *signed* integer, we
-    // make sure it's stored in an unsigned 64-bit integer variable and
-    // represents a value in the expected range.  (In reality, however,
-    // gettimeofdayWrapper() will return a positive integer that will fit
-    // in 48 bits)
-    const uint64_t now = (detail::gettimeWrapper() & 0x0000ffffffffffffULL);
+    const uint64_t now = getTSIGTime();
 
     // For responses adjust the error code.
-    if (impl_->state_ == CHECKED) {
+    if (impl_->state_ == RECEIVED_REQUEST) {
         error = impl_->error_;
     }
 
@@ -107,11 +272,10 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
                                               now, DEFAULT_FUDGE, 0, NULL,
                                               qid, error.getCode(), 0, NULL)));
         impl_->previous_digest_.clear();
-        impl_->state_ = SIGNED;
+        impl_->state_ = SENT_RESPONSE;
         return (tsig);
     }
 
-    OutputBuffer variables(0);
     HMACPtr hmac(CryptoLink::getCryptoLink().createHMAC(
                      impl_->key_.getSecret(),
                      impl_->key_.getSecretLength(),
@@ -121,87 +285,169 @@ TSIGContext::sign(const uint16_t qid, const void* const data,
     // If the context has previous MAC (either the Request MAC or its own
     // previous MAC), digest it.
     if (impl_->state_ != INIT) {
-        const uint16_t previous_digest_len(impl_->previous_digest_.size());
-        variables.writeUint16(previous_digest_len);
-        if (previous_digest_len != 0) {
-            variables.writeData(&impl_->previous_digest_[0],
-                                previous_digest_len);
-        }
-        hmac->update(variables.getData(), variables.getLength());
+        impl_->digestPreviousMAC(hmac);
     }
 
     // Digest the message (without TSIG)
     hmac->update(data, data_len);
 
-    //
-    // Digest TSIG variables.  If state_ is SIGNED we skip digesting them
-    // except for time related variables (RFC2845 4.4).
-    //
-    variables.clear();
-    if (impl_->state_ != SIGNED) {
-        impl_->key_.getKeyName().toWire(variables);
-        TSIGRecord::getClass().toWire(variables);
-        variables.writeUint32(TSIGRecord::TSIG_TTL);
-        impl_->key_.getAlgorithmName().toWire(variables);
-    }
+    // Digest TSIG variables.
+    // First, prepare some non constant variables.
     const uint64_t time_signed = (error == TSIGError::BAD_TIME()) ?
         impl_->previous_timesigned_ : now;
-    variables.writeUint16(time_signed >> 32);
-    variables.writeUint32(time_signed & 0xffffffff);
-    variables.writeUint16(DEFAULT_FUDGE);
-    hmac->update(variables.getData(), variables.getLength());
-    variables.clear();
-
-    if (impl_->state_ != SIGNED) {
-        variables.writeUint16(error.getCode());
-
-        // For BADTIME error, digest 6 bytes of other data.
-        // (6 bytes = size of time signed value)
-        variables.writeUint16((error == TSIGError::BAD_TIME()) ? 6 : 0);
-        hmac->update(variables.getData(), variables.getLength());
-
-        variables.clear();
-        if (error == TSIGError::BAD_TIME()) {
-            variables.writeUint16(now >> 32);
-            variables.writeUint32(now & 0xffffffff);
-            hmac->update(variables.getData(), variables.getLength());
-        }
+    // For BADTIME error, we include 6 bytes of other data.
+    // (6 bytes = size of time signed value)
+    const uint16_t otherlen = (error == TSIGError::BAD_TIME()) ? 6 : 0;
+    OutputBuffer otherdatabuf(otherlen);
+    if (error == TSIGError::BAD_TIME()) {
+            otherdatabuf.writeUint16(now >> 32);
+            otherdatabuf.writeUint32(now & 0xffffffff);
     }
-    const uint16_t otherlen = variables.getLength();
+    const void* const otherdata =
+        (otherlen == 0) ? NULL : otherdatabuf.getData();
+    // Then calculate the digest.  If state_ is SENT_RESPONSE we are sending
+    // a continued message in the same TCP stream so skip digesting
+    // variables except for time related variables (RFC2845 4.4).
+    impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+                               TSIGRecord::TSIG_TTL, time_signed,
+                               DEFAULT_FUDGE, error.getCode(),
+                               otherlen, otherdata,
+                               impl_->state_ == SENT_RESPONSE);
 
     // Get the final digest, update internal state, then finish.
     vector<uint8_t> digest = hmac->sign();
+    assert(digest.size() <= 0xffff); // cryptolink API should have ensured it.
     ConstTSIGRecordPtr tsig(new TSIGRecord(
                                 impl_->key_.getKeyName(),
                                 any::TSIG(impl_->key_.getAlgorithmName(),
                                           time_signed, DEFAULT_FUDGE,
                                           digest.size(), &digest[0],
                                           qid, error.getCode(), otherlen,
-                                          otherlen == 0 ?
-                                          NULL : variables.getData())));
+                                          otherdata)));
     // Exception free from now on.
     impl_->previous_digest_.swap(digest);
-    impl_->state_ = SIGNED;
+    impl_->state_ = (impl_->state_ == INIT) ? SENT_REQUEST : SENT_RESPONSE;
     return (tsig);
 }
 
-void
-TSIGContext::verifyTentative(ConstTSIGRecordPtr tsig, TSIGError error) {
-    const any::TSIG tsig_rdata = tsig->getRdata();
+TSIGError
+TSIGContext::verify(const TSIGRecord* const record, const void* const data,
+                    const size_t data_len)
+{
+    if (impl_->state_ == SENT_RESPONSE) {
+        isc_throw(TSIGContextError,
+                  "TSIG verify attempt after sending a response");
+    }
 
-    impl_->error_ = error;
-    if (error == TSIGError::BAD_TIME()) {
-        impl_->previous_timesigned_ = tsig_rdata.getTimeSigned();
+    // This case happens when we sent a signed request and have received an
+    // unsigned response.  According to RFC2845 Section 4.6 this case should be
+    // considered a "format error" (although the specific error code
+    // wouldn't matter much for the caller).
+    if (record == NULL) {
+        return (impl_->postVerifyUpdate(TSIGError::FORMERR(), NULL, 0));
+    }
+
+    const any::TSIG& tsig_rdata = record->getRdata();
+
+    // Reject some obviously invalid data
+    if (data_len < MESSAGE_HEADER_LEN + record->getLength()) {
+        isc_throw(InvalidParameter,
+                  "TSIG verify: data length is invalid: " << data_len);
+    }
+    if (data == NULL) {
+        isc_throw(InvalidParameter, "TSIG verify: empty data is invalid");
+    }
+
+    // Check key: whether we first verify it with a known key or we verify
+    // it using the consistent key in the context.  If the check fails we are
+    // done with BADKEY.
+    if (impl_->state_ == INIT && impl_->error_ == TSIGError::BAD_KEY()) {
+        return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), NULL, 0));
+    }
+    if (impl_->key_.getKeyName() != record->getName() ||
+        impl_->key_.getAlgorithmName() != tsig_rdata.getAlgorithm()) {
+        return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), NULL, 0));
+    }
+
+    // Check time: the current time must be in the range of
+    // [time signed - fudge, time signed + fudge].  Otherwise verification
+    // fails with BADTIME. (RFC2845 Section 4.6.2)
+    // Note: for simplicity we don't explicitly catch the case of too small
+    // current time causing underflow.  With the fact that fudge is quite
+    // small and (for now) non configurable, it shouldn't be a real concern
+    // in practice.
+    const uint64_t now = getTSIGTime();
+    if (tsig_rdata.getTimeSigned() + DEFAULT_FUDGE < now ||
+        tsig_rdata.getTimeSigned() - DEFAULT_FUDGE > now) {
+        const void* digest = NULL;
+        size_t digest_len = 0;
+        if (impl_->state_ == INIT) {
+            digest = tsig_rdata.getMAC();
+            digest_len = tsig_rdata.getMACSize();
+            impl_->previous_timesigned_ = tsig_rdata.getTimeSigned();
+        }
+        return (impl_->postVerifyUpdate(TSIGError::BAD_TIME(), digest,
+                                        digest_len));
+    }
+
+    // TODO: signature length check based on RFC4635
+    // (Right now we enforce the standard signature length in libcryptolink)
+
+    // Handling empty MAC.  While RFC2845 doesn't explicitly prohibit other
+    // cases, it can only reasonably happen in a response with BADSIG or
+    // BADKEY.  We reject other cases as if it were BADSIG to avoid unexpected
+    // acceptance of a bogus signature.  This behavior follows the BIND 9
+    // implementation.
+    if (tsig_rdata.getMACSize() == 0) {
+        TSIGError error = TSIGError(tsig_rdata.getError());
+        if (error != TSIGError::BAD_SIG() && error != TSIGError::BAD_KEY()) {
+            error = TSIGError::BAD_SIG();
+        }
+        return (impl_->postVerifyUpdate(error, NULL, 0));
+    }
+
+    HMACPtr hmac(CryptoLink::getCryptoLink().createHMAC(
+                     impl_->key_.getSecret(),
+                     impl_->key_.getSecretLength(),
+                     impl_->key_.getAlgorithm()),
+                 deleteHMAC);
+
+    // If the context has previous MAC (either the Request MAC or its own
+    // previous MAC), digest it.
+    if (impl_->state_ != INIT) {
+        impl_->digestPreviousMAC(hmac);
     }
 
-    // For simplicity we assume non empty digests.
-    assert(tsig_rdata.getMACSize() != 0);
-    impl_->previous_digest_.assign(
-        static_cast<const uint8_t*>(tsig_rdata.getMAC()),
-        static_cast<const uint8_t*>(tsig_rdata.getMAC()) +
-        tsig_rdata.getMACSize());
+    //
+    // Digest DNS message (excluding the trailing TSIG RR and adjusting the
+    // QID and ARCOUNT header fields)
+    //
+    impl_->digestDNSMessage(hmac, tsig_rdata.getOriginalID(),
+                            data, data_len - record->getLength());
+
+    // Digest TSIG variables.  If state_ is VERIFIED_RESPONSE, it's a
+    // continuation of the same TCP stream and skip digesting them except
+    // for time related variables (RFC2845 4.4).
+    // Note: we use the constant values for RR class and TTL specified
+    // in RFC2845, not received values (we reject other values in constructing
+    // the TSIGRecord).
+    impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(),
+                               TSIGRecord::TSIG_TTL,
+                               tsig_rdata.getTimeSigned(),
+                               tsig_rdata.getFudge(), tsig_rdata.getError(),
+                               tsig_rdata.getOtherLen(),
+                               tsig_rdata.getOtherData(),
+                               impl_->state_ == VERIFIED_RESPONSE);
 
-    impl_->state_ = CHECKED;
+    // Verify the digest with the received signature.
+    if (hmac->verify(tsig_rdata.getMAC(), tsig_rdata.getMACSize())) {
+        return (impl_->postVerifyUpdate(TSIGError::NOERROR(),
+                                        tsig_rdata.getMAC(),
+                                        tsig_rdata.getMACSize()));
+    }
+
+    return (impl_->postVerifyUpdate(TSIGError::BAD_SIG(), NULL, 0));
 }
+
 } // namespace dns
 } // namespace isc
diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h
index fbcb1bb..bceec25 100644
--- a/src/lib/dns/tsig.h
+++ b/src/lib/dns/tsig.h
@@ -17,12 +17,27 @@
 
 #include <boost/noncopyable.hpp>
 
+#include <exceptions/exceptions.h>
+
 #include <dns/tsigerror.h>
 #include <dns/tsigkey.h>
 #include <dns/tsigrecord.h>
 
 namespace isc {
 namespace dns {
+
+/// An exception that is thrown for logic errors identified in TSIG
+/// sign/verify operations.
+///
+/// Note that this exception is not thrown for TSIG protocol errors such as
+/// verification failures.  In general, this exception indicates an internal
+/// program bug.
+class TSIGContextError : public isc::Exception {
+public:
+    TSIGContextError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 /// TSIG session context.
 ///
 /// The \c TSIGContext class maintains a context of a signed session of
@@ -59,8 +74,7 @@ namespace dns {
 /// in this mode will identify the appropriate TSIG key (or internally record
 /// an error if it doesn't find a key).  The server will then verify the
 /// query with the context, and generate a signed response using the same
-/// same context.  (Note: this mode is not yet implemented and may change,
-/// see below).
+/// same context.
 ///
 /// When multiple messages belong to the same TSIG session, either side
 /// (signer or verifier) will keep using the same context.  It records
@@ -68,8 +82,68 @@ namespace dns {
 /// calls to \c sign() or \c verify() work correctly in terms of the TSIG
 /// protocol.
 ///
-/// \note The \c verify() method is not yet implemented.  The implementation
-/// and documentation should be updated in the corresponding task.
+/// \b Examples
+///
+/// This is a typical client application that sends a TSIG signed query
+/// and verifies the response.
+///
+/// \code
+///    // "renderer" is of MessageRenderer to render the message.
+///    // (TSIGKey would be configured from config or command line in real app)
+///    TSIGContext ctx(TSIGKey("key.example:MSG6Ng=="));
+///    Message message(Message::RENDER);
+///    message.addQuestion(Question(Name("www.example.com"), RRClass::IN(),
+///                                 RRType::A()));
+///    message.toWire(renderer, ctx);
+///
+///    // sendto, then recvfrom.  received result in (data, data_len)
+///
+///    message.clear(Message::PARSE);
+///    InputBuffer buffer(data, data_len);
+///    message.fromWire(buffer);
+///    TSIGError tsig_error = ctx.verify(message.getTSIGRecord(),
+///                                      data, data_len);
+///    if (tsig_error == TSIGError::NOERROR()) {
+///        // okay.  ctx can be continuously used if it's receiving subsequent
+///        // signed responses from a TCP stream.
+///    } else if (message.getRcode() == Rcode::NOTAUTH()) {
+///        // hard error.  give up this transaction per RFC2845 4.6.
+///    } else {
+///        // Other error: discard response keep waiting with the same ctx
+///        // for another (again, RFC2845 4.6).
+///    } \endcode
+///
+/// And this is a typical server application that authenticates a signed
+/// query and returns a response according to the result.
+///
+/// \code
+///    // Assume "message" is of type Message for query handling and
+///    // "renderer" is of MessageRenderer to render responses.
+///    Message message(Message::RENDER);
+///
+///    TSIGKeyRing keyring; // this must be configured with keys somewhere
+///
+///    // Receive a query and store it in (data, data_len)
+///    InputBuffer buffer(data, data_len);
+///    message.clear(Message::PARSE);
+///    message.fromWire(buffer);
+///
+///    const TSIGRecord* tsig = message.getTSIGRecord();
+///    if (tsig != NULL) {
+///        TSIGContext ctx(tsig->getName(), tsig->getRdata().getAlgorithm(),
+///                        keyring);
+///        ctx.verify(tsig, data, data_len);
+///
+///        // prepare response
+///        message.makeResponse();
+///        //...
+///        message.toWire(renderer, ctx);
+///
+///        // send the response data back to the client.
+///        // If this is a beginning of a signed session over a TCP and
+///        // server has more data to send to the client, this ctx
+///        // will be used to sign subsequent messages.
+///    } \endcode
 ///
 /// <b>TCP Consideration</b>
 ///
@@ -110,8 +184,10 @@ public:
     /// directly.
     enum State {
         INIT,                   ///< Initial state
-        SIGNED,                 ///< Sign completed
-        CHECKED ///< Verification completed (may or may not successfully)
+        SENT_REQUEST, ///< Client sent a signed request, waiting response
+        RECEIVED_REQUEST,       ///< Server received a signed request
+        SENT_RESPONSE,          ///< Server sent a signed response
+        VERIFIED_RESPONSE       ///< Client successfully verified a response
     };
 
     /// \name Constructors and destructor
@@ -124,6 +200,10 @@ public:
     /// \param key The TSIG key to be used for TSIG sessions with this context.
     explicit TSIGContext(const TSIGKey& key);
 
+    /// Constructor from key parameters and key ring.
+    TSIGContext(const Name& key_name, const Name& algorithm_name,
+                const TSIGKeyRing& keyring);
+
     /// The destructor.
     ~TSIGContext();
     //@}
@@ -141,6 +221,13 @@ public:
     /// complete TSIG RR into the message that has been signed so that it
     /// will become a complete TSIG-signed message.
     ///
+    /// In general, this method is called once by a client to send a
+    /// signed request or one more times by a server to sign
+    /// response(s) to a signed request.  To avoid allowing accidental
+    /// misuse, if this method is called after a "client" validates a
+    /// response, an exception of class \c TSIGContextError will be
+    /// thrown.
+    ///
     /// \note Normal applications are not expected to call this method
     /// directly; they will usually use the \c Message::toWire() method
     /// with a \c TSIGContext object being a parameter and have the
@@ -165,6 +252,7 @@ public:
     /// returns (without an exception being thrown), the internal state of
     /// the \c TSIGContext won't be modified.
     ///
+    /// \exception TSIGContextError Context already verified a response.
     /// \exception InvalidParameter \c data is NULL or \c data_len is 0
     /// \exception cryptolink::LibraryError Some unexpected error in the
     /// underlying crypto operation
@@ -179,6 +267,92 @@ public:
     ConstTSIGRecordPtr sign(const uint16_t qid, const void* const data,
                             const size_t data_len);
 
+    /// Verify a DNS message.
+    ///
+    /// This method verifies given data along with the context and a given
+    /// TSIG in the form of a \c TSIGRecord object.  The data to be verified
+    /// is generally expected to be a complete, wire-format DNS message,
+    /// exactly as received by the host, and ending with a TSIG RR.
+    /// After verification process this method updates its internal state,
+    /// and returns the result in the form of a \c TSIGError object.
+    /// Possible return values are (see the \c TSIGError class description
+    /// for the mnemonics):
+    ///
+    /// - \c NOERROR: The data has been verified correctly.
+    /// - \c FORMERR: \c TSIGRecord is not given (see below).
+    /// - \c BAD_KEY: Appropriate key is not found or specified key doesn't
+    ///               match for the data.
+    /// - \c BAD_TIME: The current time doesn't fall in the range specified
+    ///                in the TSIG.
+    /// - \c BAD_SIG: The signature given in the TSIG doesn't match against
+    ///               the locally computed digest or is the signature is
+    ///               invalid in other way.
+    ///
+    /// If this method is called by a DNS client waiting for a signed
+    /// response and the result is not \c NOERROR, the context can be used
+    /// to try validating another signed message as described in RFC2845
+    /// Section 4.6.
+    ///
+    /// If this method is called by a DNS server that tries to authenticate
+    /// a signed request, and if the result is not \c NOERROR, the
+    /// corresponding error condition is recorded in the context so that
+    /// the server can return a response indicating what was wrong by calling
+    /// \c sign() with the updated context.
+    ///
+    /// In general, this method is called once by a server for
+    /// authenticating a signed request or one more times by a client to
+    /// validate signed response(s) to a signed request.  To avoid allowing
+    /// accidental misuse, if this method is called after a "server" signs
+    /// a response, an exception of class \c TSIGContextError will be thrown.
+    ///
+    /// The \c record parameter can be NULL; in that case this method simply
+    /// returns \c FORMERR as the case described in Section 4.6 of RFC2845,
+    /// i.e., receiving an unsigned response to a signed request.  This way
+    /// a client can transparently pass the result of
+    /// \c Message::getTSIGRecord() without checking whether it's non NULL
+    /// and take an appropriate action based on the result of this method.
+    ///
+    /// This method handles the given data mostly as opaque.  It digests
+    /// the data assuming it begins with a DNS header and ends with a TSIG
+    /// RR whose length is given by calling \c TSIGRecord::getLength() on
+    /// \c record, but otherwise it doesn't parse the data to confirm the
+    /// assumption.  It's caller's responsibility to ensure the data is
+    /// valid and consistent with \c record.  To avoid disruption, this
+    /// method performs minimal validation on the given \c data and \c record:
+    /// \c data must not be NULL; \c data_len must not be smaller than the
+    /// sum of the DNS header length (fixed, 12 octets) and the length of
+    /// the TSIG RR.  If this check fails it throws an \c InvalidParameter
+    /// exception.
+    ///
+    /// One unexpected case that is not covered by this method is that a
+    /// client receives a signed response to an unsigned request.  RFC2845 is
+    /// silent about such cases; BIND 9 explicitly identifies the case and
+    /// rejects it.  With this implementation, the client can know that the
+    /// response contains a TSIG via the result of
+    /// \c Message::getTSIGRecord() and that it is an unexpected TSIG due to
+    /// the fact that it doesn't have a corresponding \c TSIGContext.
+    /// It's up to the client implementation whether to react to such a case
+    /// explicitly (for example, it could either ignore the TSIG and accept
+    /// the response or drop it).
+    ///
+    /// This method provides the strong exception guarantee; unless the method
+    /// returns (without an exception being thrown), the internal state of
+    /// the \c TSIGContext won't be modified.
+    ///
+    /// \todo Support intermediate TCP DNS messages without TSIG (RFC2845 4.4)
+    /// \todo Signature truncation support based on RFC4635
+    ///
+    /// \exception TSIGContextError Context already signed a response.
+    /// \exception InvalidParameter \c data is NULL or \c data_len is too small.
+    ///
+    /// \param record The \c TSIGRecord to be verified with \c data
+    /// \param data Points to the wire-format data (exactly as received) to
+    /// be verified
+    /// \param data_len The length of \c data in bytes
+    /// \return The \c TSIGError that indicates verification result
+    TSIGError verify(const TSIGRecord* const record, const void* const data,
+                     const size_t data_len);
+
     /// Return the current state of the context
     ///
     /// \note
@@ -196,18 +370,6 @@ public:
     /// \exception None
     TSIGError getError() const;
 
-    // This method is tentatively added for testing until a complete
-    // verify() method is implemented.  Once it's done this should be
-    // removed, and corresponding tests should be updated.
-    //
-    // This tentative "verify" method changes the internal state of
-    // the TSIGContext to the CHECKED as if it were verified (though possibly
-    // unsuccessfully) with given tsig_rdata.  If the error parameter is
-    // given and not NOERROR, it's recorded inside the context so that the
-    // subsequent sign() will behave accordingly.
-    void verifyTentative(ConstTSIGRecordPtr tsig,
-                         TSIGError error = TSIGError::NOERROR());
-
     /// \name Protocol constants and defaults
     ///
     //@{
diff --git a/src/lib/dns/tsigerror.cc b/src/lib/dns/tsigerror.cc
index e63c9ab..36ef47d 100644
--- a/src/lib/dns/tsigerror.cc
+++ b/src/lib/dns/tsigerror.cc
@@ -49,6 +49,17 @@ TSIGError::toText() const {
     }
 }
 
+Rcode
+TSIGError::toRcode() const {
+    if (code_ <= MAX_RCODE_FOR_TSIGERROR) {
+        return (Rcode(code_));
+    }
+    if (code_ > BAD_TIME_CODE) {
+        return (Rcode::SERVFAIL());
+    }
+    return (Rcode::NOTAUTH());
+}
+
 std::ostream&
 operator<<(std::ostream& os, const TSIGError& error) {
     return (os << error.toText());
diff --git a/src/lib/dns/tsigerror.h b/src/lib/dns/tsigerror.h
index 4463daf..8efd3ae 100644
--- a/src/lib/dns/tsigerror.h
+++ b/src/lib/dns/tsigerror.h
@@ -22,17 +22,11 @@
 
 namespace isc {
 namespace dns {
-
-class RRClass;
-
 /// TSIG errors
 ///
 /// The \c TSIGError class objects represent standard errors related to
 /// TSIG protocol operations as defined in related specifications, mainly
 /// in RFC2845.
-///
-/// (RCODEs) of the header section of DNS messages, and extended response
-/// codes as defined in the EDNS specification.
 class TSIGError {
 public:
     /// Constants for pre-defined TSIG error values.
@@ -58,7 +52,7 @@ public:
     ///
     /// \exception None
     ///
-    /// \param code The underlying 16-bit error code value of the \c TSIGError.
+    /// \param error_code The underlying 16-bit error code value of the \c TSIGError.
     explicit TSIGError(uint16_t error_code) : code_(error_code) {}
 
     /// Constructor from \c Rcode.
@@ -125,6 +119,22 @@ public:
     /// \return A string representation of the \c TSIGError.
     std::string toText() const;
 
+    /// \brief Convert the \c TSIGError to a \c Rcode
+    ///
+    /// This method returns an \c Rcode object that is corresponding to
+    /// the TSIG error.  The returned \c Rcode is expected to be used
+    /// by a verifying server to specify the RCODE of a response when
+    /// TSIG verification fails.
+    ///
+    /// Specifically, this method returns \c Rcode::NOTAUTH() for the
+    /// TSIG specific errors, BADSIG, BADKEY, BADTIME, as described in
+    /// RFC2845.  For errors derived from the standard Rcode (code 0-15),
+    /// it returns the corresponding \c Rcode.  For others, this method
+    /// returns \c Rcode::SERVFAIL() as a last resort.
+    ///
+    /// \exception None
+    Rcode toRcode() const;
+
     /// A constant TSIG error object derived from \c Rcode::NOERROR()
     static const TSIGError& NOERROR();
 
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
index 0dbb6ad..0582c36 100644
--- a/src/lib/dns/tsigkey.cc
+++ b/src/lib/dns/tsigkey.cc
@@ -42,6 +42,7 @@ namespace {
         if (name == TSIGKey::HMACSHA256_NAME()) {
             return (isc::cryptolink::SHA256);
         }
+<<<<<<< HEAD
         if (name == TSIGKey::HMACSHA224_NAME()) {
             return (isc::cryptolink::SHA224);
         }
@@ -54,6 +55,9 @@ namespace {
  
         isc_throw(InvalidParameter,
                   "Unknown TSIG algorithm is specified: " << name);
+=======
+        return (isc::cryptolink::UNKNOWN_HASH);
+>>>>>>> master
     }
 }
 
@@ -84,7 +88,13 @@ TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name,
     if ((secret != NULL && secret_len == 0) ||
         (secret == NULL && secret_len != 0)) {
         isc_throw(InvalidParameter,
-                  "TSIGKey secret and its length are inconsistent");
+                  "TSIGKey secret and its length are inconsistent: " <<
+                  key_name << ":" << algorithm_name);
+    }
+    if (algorithm == isc::cryptolink::UNKNOWN_HASH && secret_len != 0) {
+        isc_throw(InvalidParameter,
+                  "TSIGKey with unknown algorithm has non empty secret: " <<
+                  key_name << ":" << algorithm_name);
     }
     impl_ = new TSIGKeyImpl(key_name, algorithm_name, algorithm, secret,
                             secret_len);
@@ -121,8 +131,15 @@ TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) {
         vector<uint8_t> secret;
         isc::util::encode::decodeBase64(secret_str, secret);
 
+        if (algorithm == isc::cryptolink::UNKNOWN_HASH && !secret.empty()) {
+            isc_throw(InvalidParameter,
+                      "TSIG key with unknown algorithm has non empty secret: "
+                      << str);
+        }
+
         impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
-                                &secret[0], secret.size());
+                                secret.empty() ? NULL : &secret[0],
+                                secret.size());
     } catch (const Exception& e) {
         // 'reduce' the several types of exceptions name parsing and
         // Base64 decoding can throw to just the InvalidParameter
@@ -258,10 +275,11 @@ TSIGKeyRing::remove(const Name& key_name) {
 }
 
 TSIGKeyRing::FindResult
-TSIGKeyRing::find(const Name& key_name) {
+TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const {
     TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
         impl_->keys.find(key_name);
-    if (found == impl_->keys.end()) {
+    if (found == impl_->keys.end() ||
+        (*found).second.getAlgorithmName() != algorithm_name) {
         return (FindResult(NOTFOUND, NULL));
     }
     return (FindResult(SUCCESS, &((*found).second)));
diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h
index e29e549..31211d1 100644
--- a/src/lib/dns/tsigkey.h
+++ b/src/lib/dns/tsigkey.h
@@ -68,12 +68,30 @@ public:
     //@{
     /// \brief Constructor from key parameters
     ///
-    /// In the current implementation, \c algorithm_name must be a known
-    /// algorithm to this implementation, which are defined via the
-    /// <code>static const</code> member functions.  For other names
-    /// an exception of class \c InvalidParameter will be thrown.
-    /// Note: This restriction may be too strict, and we may revisit it
-    /// later.
+    /// \c algorithm_name should generally be a known algorithm to this
+    /// implementation, which are defined via the
+    /// <code>static const</code> member functions.
+    ///
+    /// Other names are still accepted as long as the secret is empty
+    /// (\c secret is \c NULL and \c secret_len is 0), however; in some cases
+    /// we might want to treat just the pair of key name and algorithm name
+    /// opaquely, e.g., when generating a response TSIG with a BADKEY error
+    /// because the algorithm is unknown as specified in Section 3.2 of
+    /// RFC2845 (in which case the algorithm name would be copied from the
+    /// request to the response, and for that purpose it would be convenient
+    /// if a \c TSIGKey object can hold a name for an "unknown" algorithm).
+    ///
+    /// \note RFC2845 does not specify which algorithm name should be used
+    /// in such a BADKEY response.  The behavior of using the same algorithm
+    /// is derived from the BIND 9 implementation.
+    ///
+    /// It is unlikely that a TSIG key with an unknown algorithm is of any
+    /// use with actual crypto operation, so care must be taken when dealing
+    /// with such keys. (The restriction for the secret will prevent
+    /// accidental creation of such a dangerous key, e.g., due to misspelling
+    /// in a configuration file).
+    /// If the given algorithm name is unknown and non empty secret is
+    /// specified, an exception of type \c InvalidParameter will be thrown.
     ///
     /// \c secret and \c secret_len must be consistent in that the latter
     /// is 0 if and only if the former is \c NULL;
@@ -98,9 +116,12 @@ public:
     /// <name>:<secret>[:<algorithm>]
     /// Where <name> is a domain name for the key, <secret> is a
     /// base64 representation of the key secret, and the optional
-    /// algorithm is an algorithm identifier as specified in RFC4635
+    /// algorithm is an algorithm identifier as specified in RFC4635.
     /// The default algorithm is hmac-md5.sig-alg.reg.int.
     ///
+    /// The same restriction about the algorithm name (and secret) as that
+    /// for the other constructor applies.
+    ///
     /// Since ':' is used as a separator here, it is not possible to
     /// use this constructor to create keys with a ':' character in
     /// their name.
@@ -307,7 +328,9 @@ public:
     /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
     ///
     /// It searches the internal storage for a \c TSIGKey whose name is
-    /// \c key_name, and returns the result in the form of a \c FindResult
+    /// \c key_name and that uses the hash algorithm identified by
+    /// \c algorithm_name.
+    /// It returns the result in the form of a \c FindResult
     /// object as follows:
     /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
     /// - \c key: A pointer to the found \c TSIGKey object if one is found;
@@ -321,8 +344,9 @@ public:
     /// This method never throws an exception.
     ///
     /// \param key_name The name of the key to be found.
+    /// \param algorithm_name The name of the algorithm of the found key.
     /// \return A \c FindResult object enclosing the search result (see above).
-    FindResult find(const Name& key_name);
+    FindResult find(const Name& key_name, const Name& algorithm_name) const;
 private:
     struct TSIGKeyRingImpl;
     TSIGKeyRingImpl* impl_;
diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc
index 40ea6c2..9dd3f78 100644
--- a/src/lib/dns/tsigrecord.cc
+++ b/src/lib/dns/tsigrecord.cc
@@ -70,14 +70,16 @@ castToTSIGRdata(const rdata::Rdata& rdata) {
 }
 
 TSIGRecord::TSIGRecord(const Name& name, const RRClass& rrclass,
-                       const RRTTL&, // we ignore TTL
-                       const rdata::Rdata& rdata,
+                       const RRTTL& ttl, const rdata::Rdata& rdata,
                        size_t length) :
     key_name_(name), rdata_(castToTSIGRdata(rdata)), length_(length)
 {
     if (rrclass != getClass()) {
         isc_throw(DNSMessageFORMERR, "Unexpected TSIG RR class: " << rrclass);
     }
+    if (ttl != RRTTL(TSIG_TTL)) {
+        isc_throw(DNSMessageFORMERR, "Unexpected TSIG TTL: " << ttl);
+    }
 }
 
 const RRClass&
diff --git a/src/lib/dns/tsigrecord.h b/src/lib/dns/tsigrecord.h
index e42edf1..03de746 100644
--- a/src/lib/dns/tsigrecord.h
+++ b/src/lib/dns/tsigrecord.h
@@ -83,15 +83,39 @@ public:
     /// TSIG).
     ///
     /// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0).
-    /// If the RR class is different from the expected one, this
+    /// If the RR class or TTL is different from the expected one, this
     /// implementation considers it an invalid record and throws an exception
-    /// of class \c DNSMessageFORMERR.  On the other hand, the TTL is simply
-    /// ignored (in that sense we could even omit that parameter, but it's
-    /// still included if and when we want to change the policy).  RFC2845
-    /// is silent about what the receiver should do if it sees an unexpected
-    /// RR class or TTL in a TSIG RR.  This implementation simply follows what
-    /// BIND 9 does (it is not clear why BIND 9 employs the "inconsistent"
-    /// policy).
+    /// of class \c DNSMessageFORMERR.
+    ///
+    /// \note This behavior is not specified in the protocol specification,
+    /// but this implementation rejects unexpected values for the following
+    /// reasons (but in any case, this won't matter much in practice as
+    /// RFC2848 clearly states these fields always have the fixed values and
+    /// any sane implementation of TSIG signer will follow that):
+    /// According to the RFC editor (in a private communication), the intended
+    /// use of the TSIG TTL field is to signal protocol extensions (currently
+    /// no such extension is defined), so this field may actually be
+    /// validly non 0 in future.  However, until the implementation supports
+    /// that extension it may not always be able to handle the extended
+    /// TSIG as intended; the extension may even affect digest computation.
+    /// There's a related issue on this point.  Different implementations
+    /// interpret the RFC in different ways on the received TTL when
+    /// digesting the message: BIND 9 uses the received value (even if
+    /// it's not 0) as part of the TSIG variables; NLnet Labs' LDNS and NSD
+    /// always use a fixed constant of 0 regardless of the received TTL value.
+    /// This means if and when an extension with non 0 TTL is introduced
+    /// there will be interoperability problems in the form of verification
+    /// failure.  By explicitly rejecting it (and subsequently returning
+    /// a response with a format error) we can indicate the source of the
+    /// problem more clearly than a "bad signature" TSIG error, which can
+    /// happen for various reasons.  On the other hand, rejecting unexpected
+    /// RR classes is mostly for consistency; the RFC lists these two fields
+    /// in the same way, so it makes more sense to handle them equally.
+    /// (BIND 9 rejects unexpected RR classes for TSIG, but that is part of
+    /// general check on RR classes on received RRs; it generally requests
+    /// all classes are the same, and if the protocol specifies the use of
+    /// a particular class for a particular type of RR, it requests that
+    /// class be used).
     ///
     /// Likewise, if \c rdata is not of type \c any::TSIG, an exception of
     /// class DNSMessageFORMERR will be thrown.  When the caller is a
@@ -142,8 +166,7 @@ public:
     /// \param name The owner name of the TSIG RR
     /// \param rrclass The RR class of the RR.  Must be \c RRClass::ANY()
     /// (see above)
-    /// \param ttl The TTL of the RR.  Expected to be a zero TTL, but
-    /// actually ignored in this implementation.
+    /// \param ttl The TTL of the RR.  Must be 0 (see above)
     /// \param rdata The RDATA of the RR.  Must be of type \c any::TSIG.
     /// \param length The size of the RR (see above)
     TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl,
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 537d08f..c27b3e4 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -16,7 +16,7 @@ liblog_la_SOURCES += logger_impl.cc logger_impl.h
 liblog_la_SOURCES += logger_support.cc logger_support.h
 liblog_la_SOURCES += messagedef.cc messagedef.h
 liblog_la_SOURCES += message_dictionary.cc message_dictionary.h
-liblog_la_SOURCES += message_exception.h message_exception.cc
+liblog_la_SOURCES += message_exception.h
 liblog_la_SOURCES += message_initializer.cc message_initializer.h
 liblog_la_SOURCES += message_reader.cc message_reader.h
 liblog_la_SOURCES += message_types.h
diff --git a/src/lib/log/README b/src/lib/log/README
index d01b12f..529eefc 100644
--- a/src/lib/log/README
+++ b/src/lib/log/README
@@ -44,7 +44,7 @@ stored definitions; this updated text is logged.  However, to aid support,
 the message identifier so in the example above, the message finally logged
 would be something like:
 
-    OPENIN, unable to open a.txt for input
+    FAC_OPENIN, unable to open a.txt for input
 
 
 Using The System
@@ -55,17 +55,15 @@ The steps in using the system are:
    mnemonic for the message, typically 6-12 characters long - and a message.
    The file is described in more detail below.
 
-   Ideally the file should have a file type of ".msg".
+   Ideally the file should have a file type of ".mes".
 
-2. Run it through the message compiler to produce the .h and .cc files.  It
-   is intended that this step be included in the build process.  However,
-   for now run the compiler (found in the "compiler" subdirectory) manually.
-   The only argument is the name of the message file: it will produce as
-   output two files, having the same name as the input file but with file
-   types of ".h" and ".cc".
-
-   The compiler is built in the "compiler" subdirectory of the "src/lib/log"
-   directory.
+2. Run it through the message compiler to produce the .h and .cc files.  This
+   step should be included in the build process. (For an example of how to
+   do this, see src/lib/nsas/Makefile.am.) During development though, the
+   message compiler (found in the "src/lib/log/compiler" directory) will need
+   to be run manually.  The only argument is the name of the message file: it
+   will produce as output two files in the default directory, having the same
+   name as the input file but with file types of ".h" and ".cc".
 
 3. Include the .h file in your source code to define message symbols, and
    make sure that the .cc file is compiled and linked into your program -
@@ -96,11 +94,12 @@ An example file could be:
 
 $PREFIX TEST_
 $NAMESPACE isc::log
-TEST1       message %1 is much too large
-+ This message is a test for the general message code
 
-UNKNOWN     unknown message
-+ Issued when the message is unknown.
+% TEST1       message %1 is much too large
+This message is a test for the general message code
+
+% UNKNOWN     unknown message
+Issued when the message is unknown.
 
 -- END --
 
@@ -117,23 +116,31 @@ Points to note:
 
 * Lines starting $ are directives.  At present, two directives are recognised:
 
-  * $PREFIX, which has one argument: the string used to prefix symbols.  If
-    absent, there is no prefix to the symbols. (Prefixes are explained below.)
+  * $PREFIX, which has one optional argument: the string used to prefix symbols.
+    If absent, there is no prefix to the symbols. (Prefixes are explained below.)
+
   * $NAMESPACE, which has one argument: the namespace in which the symbols are
-    created.  In the absence of a $NAMESPACE directive, symbols will be put
-    in the global namespace.
+    created.  In the absence of a $NAMESPACE directive, symbols will be put in
+    the anonymous namespace.
 
-* Lines starting + indicate an explanation for the preceding message.  These
-  are intended to be processed by a separate program and used to generate
-  an error messages manual.  However they are treated like comments by the
-  message compiler.  As with comments, these must be on a line by themselves;
-  if inline, the text (including the leading "+") will be interpreted as
-  part of the line.
+* Message lines.  These start with a "%" and are followed by the message
+  identification and the message text, the latter including zero or more
+  replacement tokens, e.g.
 
-* Message lines.  These comprise a symbol name and a message, which may
-  include zero or more %1, %2...  placeholder tokens.  Symbol names will be
-  upper-cased by the compiler.
+     % TEST  message %1 is larger than the permitted length of %2
 
+  * There may be zero or more spaces between the leading "%" and the message
+    identification (which, in the example above, is the word "TEST").
+
+  * The replacement tokens are the strings "%1", "%2" etc.  When a message
+    is logged, these are replaced with the arguments passed to the logging
+    call: %1 refers to the first argument, %2 to the second etc.  Within the
+    message text, the placeholders can appear in any order, and placeholders
+    can be repeated.
+     
+* Remaining lines indicate an explanation for the preceding message.  These
+  are intended to be processed by a separate program and used to generate
+  an error messages manual.  They are ignored by the message compiler.
 
 Message Compiler
 ----------------
@@ -153,9 +160,8 @@ the form:
       :
    }
 
-The symbols define the keys in the global message dictionary.
-
-The namespace enclosing the symbols is set by the $NAMESPACE directive.
+The symbols define the keys in the global message dictionary, with the
+namespace enclosing the symbols set by the $NAMESPACE directive.
 
 The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
 the argument to the directive.  So "$PREFIX MSG_" would prefix the identifier
@@ -163,12 +169,20 @@ ABC with "MSG_" to give the symbol MSG_ABC.  Similarly "$PREFIX E" would
 prefix it with "E" to give the symbol EABC.  If no $PREFIX is given, no
 prefix appears (so the symbol in this example would be ABC).
 
+The prefix is "syntactic sugar".  Generally all symbols in a given message file
+will be prefixed with the same set of letters.  By extracting these into
+a separate prefix, it becomes easier to disambiguate the different symbols.
+
+There may be multiple $PREFIX directives in a file.  A $PREFIX directive applies
+to all message definitions between it an the next $PREFIX directive.  A $PREFIX
+directive with no arguments clears the current prefix.
+
 2) A C++ source file (called <message-file-name>.cc) that holds the definitions
 of the global symbols and code to insert the symbols and messages into the map.
 
 Symbols are defined to be equal to strings holding the identifier, e.g.
 
-   extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
+   extern const isc::log::MessageID MSG_DUPLNS = "MSG_DUPLNS";
 
 (The implementation allows symbols to be compared.  However, use of strings
 should not be assumed - a future implementation may change this.)
@@ -243,12 +257,10 @@ To use the current version of the logging:
       highest-level debug messages and 99 for the lowest-level (and typically
       more verbose) messages.
 
-   c) Name of an external message file.  This is the same as a standard message
-      file, although it should not include any directives. (A single directive
-      of a particular type will be ignored; multiple directives will cause the
-      read of the file to fail with an error.)  If a message is replaced, the
-      message should include the same printf-format directives in the same order
-      as the original message.
+   c) The external message file.  If present, this is the same as a standard
+      message file, although it should not include any directives. (A single
+      directive of a particular type will be ignored; multiple directives will
+      cause the read of the file to fail with an error.)
 
 4. Issue logging calls using methods on logger, e.g.
 
@@ -264,6 +276,30 @@ To use the current version of the logging:
    At present, the only logging is to the console.
 
 
+Efficiency Considerations
+-------------------------
+A common pattern in logging is a debug call of the form:
+
+   logger.debug(dbglevel, MSGID).arg(expensive_call()).arg(...
+
+... where "expensive_call()" is a function call to obtain logging information
+that may be computationally intensive.  Although the cost may be justified
+when debugging is enabled, the cost is still incurred even if debugging is
+disabled and the debug() method returns without outputting anything.  (The
+same may be true of other logging levels, although there are likely to be
+fewer calls to logger.info(), logger.error() etc. throughout the code and
+they are less likely to be disabled.)
+
+For this reason, a set of macros is provided and are called using the
+construct:
+
+   LOG_DEBUG(logger, dbglevel, MSGID).arg(expensive_call()).arg(...
+   LOG_INFO(logger, MSGID).arg(expensive_call()...)
+
+If these are used, the arguments passed to the arg() method are not evaluated
+if the relevant logging level is disabled.
+
+
 Severity Guidelines
 ===================
 When using logging, the question arises, what severity should a message be
@@ -361,9 +397,6 @@ is only one piece of code that does this functionality.
 Outstanding Issues
 ==================
 * Ability to configure system according to configuration database.
-* Update the build procedure to create .cc and .h files from the .msg file
-  during the build process. (Requires that the message compiler is built
-  first.)
 
 
 log4cxx Issues
diff --git a/src/lib/log/compiler/message.cc b/src/lib/log/compiler/message.cc
index eb9ddca..457a62e 100644
--- a/src/lib/log/compiler/message.cc
+++ b/src/lib/log/compiler/message.cc
@@ -50,17 +50,13 @@ static const char* VERSION = "1.0-0";
 /// \li A .cc file containing code that adds the messages to the program's
 /// message dictionary at start-up time.
 ///
-/// Alternatively, the program can produce a .py file that contains the
-/// message definitions.
-///
-
 /// \b Invocation<BR>
 /// The program is invoked with the command:
 ///
 /// <tt>message [-v | -h | \<message-file\>]</tt>
 ///
-/// It reads the message file and writes out two files of the same name but with
-/// extensions of .h and .cc.
+/// It reads the message file and writes out two files of the same name in the
+/// default directory but with extensions of .h and .cc.
 ///
 /// \-v causes it to print the version number and exit. \-h prints a help
 /// message (and exits).
@@ -251,14 +247,13 @@ writeClosingNamespace(ostream& output, const vector<string>& ns) {
 ///
 /// \param file Name of the message file.  The header file is written to a
 /// file of the same name but with a .h suffix.
-/// \param prefix Prefix string to use in symbols
 /// \param ns Namespace in which the definitions are to be placed.  An empty
 /// string indicates no namespace.
 /// \param dictionary Dictionary holding the message definitions.
 
 void
-writeHeaderFile(const string& file, const string& prefix,
-        const vector<string>& ns_components, MessageDictionary& dictionary)
+writeHeaderFile(const string& file, const vector<string>& ns_components,
+                MessageDictionary& dictionary)
 {
     Filename message_file(file);
     Filename header_file(Filename(message_file.name()).useAsDefault(".h"));
@@ -271,7 +266,7 @@ writeHeaderFile(const string& file, const string& prefix,
 
     try {
         if (hfile.fail()) {
-            throw MessageException(MSG_OPNMSGOUT, header_file.fullName(),
+            throw MessageException(MSG_OPENOUT, header_file.fullName(),
                 strerror(errno));
         }
 
@@ -294,7 +289,7 @@ writeHeaderFile(const string& file, const string& prefix,
         vector<string> idents = sortedIdentifiers(dictionary);
         for (vector<string>::const_iterator j = idents.begin();
             j != idents.end(); ++j) {
-            hfile << "extern const isc::log::MessageID " << prefix << *j << ";\n";
+            hfile << "extern const isc::log::MessageID " << *j << ";\n";
         }
         hfile << "\n";
 
@@ -305,7 +300,7 @@ writeHeaderFile(const string& file, const string& prefix,
 
         // Report errors (if any) and exit
         if (hfile.fail()) {
-            throw MessageException(MSG_MSGWRTERR, header_file.fullName(),
+            throw MessageException(MSG_WRITERR, header_file.fullName(),
                 strerror(errno));
         }
 
@@ -354,8 +349,8 @@ replaceNonAlphaNum(char c) {
 /// to it.  But until BIND-10 is ported to Windows, we won't know.
 
 void
-writeProgramFile(const string& file, const string& prefix,
-    const vector<string>& ns_components, MessageDictionary& dictionary)
+writeProgramFile(const string& file, const vector<string>& ns_components,
+                 MessageDictionary& dictionary)
 {
     Filename message_file(file);
     Filename program_file(Filename(message_file.name()).useAsDefault(".cc"));
@@ -364,7 +359,7 @@ writeProgramFile(const string& file, const string& prefix,
     ofstream ccfile(program_file.fullName().c_str());
     try {
         if (ccfile.fail()) {
-            throw MessageException(MSG_OPNMSGOUT, program_file.fullName(),
+            throw MessageException(MSG_OPENOUT, program_file.fullName(),
                 strerror(errno));
         }
 
@@ -387,7 +382,7 @@ writeProgramFile(const string& file, const string& prefix,
         vector<string> idents = sortedIdentifiers(dictionary);
         for (vector<string>::const_iterator j = idents.begin();
             j != idents.end(); ++j) {
-            ccfile << "extern const isc::log::MessageID " << prefix << *j <<
+            ccfile << "extern const isc::log::MessageID " << *j <<
                 " = \"" << *j << "\";\n";
         }
         ccfile << "\n";
@@ -422,7 +417,7 @@ writeProgramFile(const string& file, const string& prefix,
 
         // Report errors (if any) and exit
         if (ccfile.fail()) {
-            throw MessageException(MSG_MSGWRTERR, program_file.fullName(),
+            throw MessageException(MSG_WRITERR, program_file.fullName(),
                 strerror(errno));
         }
 
@@ -518,13 +513,10 @@ main(int argc, char* argv[]) {
         vector<string> ns_components = splitNamespace(reader.getNamespace());
 
         // Write the header file.
-        writeHeaderFile(message_file, reader.getPrefix(), ns_components,
-            dictionary);
+        writeHeaderFile(message_file, ns_components, dictionary);
 
         // Write the file that defines the message symbols and text
-        writeProgramFile(message_file, reader.getPrefix(), ns_components,
-            dictionary);
-
+        writeProgramFile(message_file, ns_components, dictionary);
 
         // Finally, warn of any duplicates encountered.
         warnDuplicates(reader);
diff --git a/src/lib/log/log_formatter.h b/src/lib/log/log_formatter.h
index 07a024c..cda1d96 100644
--- a/src/lib/log/log_formatter.h
+++ b/src/lib/log/log_formatter.h
@@ -125,7 +125,11 @@ public:
     ///
     /// \param arg The argument to place into the placeholder.
     template<class Arg> Formatter& arg(const Arg& value) {
-        return (arg(boost::lexical_cast<std::string>(value)));
+        if (logger_) {
+            return (arg(boost::lexical_cast<std::string>(value)));
+        } else {
+            return (*this);
+        }
     }
     /// \brief String version of arg.
     Formatter& arg(const std::string& arg) {
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
index 83147aa..e17c47d 100644
--- a/src/lib/log/logger_support.cc
+++ b/src/lib/log/logger_support.cc
@@ -24,7 +24,9 @@
 /// These functions will be replaced once the code has been written to obtain
 /// the logging parameters from the configuration database.
 
+#include <iostream>
 #include <algorithm>
+#include <iostream>
 #include <string>
 #include <vector>
 #include <boost/lexical_cast.hpp>
@@ -85,8 +87,12 @@ readLocalMessageFile(const char* file) {
             logger.error(ident).arg(args[0]);
             break;
 
-        default:    // 2 or more (2 should be the maximum)
+        case 2:
             logger.error(ident).arg(args[0]).arg(args[1]);
+            break;
+
+        default:    // 3 or more (3 should be the maximum)
+            logger.error(ident).arg(args[0]).arg(args[1]).arg(args[2]);
         }
     }
 }
@@ -128,5 +134,76 @@ initLogger(const string& root, isc::log::Severity severity, int dbglevel,
     }
 }
 
+/// Logger Run-Time Initialization via Environment Variables
+void initLogger() {
+
+    // Root logger name is defined by the environment variable B10_LOGGER_ROOT.
+    // If not present, the name is "b10root".
+    const char* DEFAULT_ROOT = "b10root";
+    const char* root = getenv("B10_LOGGER_ROOT");
+    if (! root) {
+        root = DEFAULT_ROOT;
+    }
+
+    // Set the logging severity.  The environment variable is
+    // B10_LOGGER_SEVERITY, and can be one of "DEBUG", "INFO", "WARN", "ERROR"
+    // of "FATAL".  Note that the string must be in upper case with no leading
+    // of trailing blanks.
+    isc::log::Severity severity = isc::log::DEFAULT;
+    const char* sev_char = getenv("B10_LOGGER_SEVERITY");
+    if (sev_char) {
+        string sev_string(sev_char);
+        if (sev_string == "DEBUG") {
+            severity = isc::log::DEBUG;
+        } else if (sev_string == "INFO") {
+            severity = isc::log::INFO;
+        } else if (sev_string == "WARN") {
+            severity = isc::log::WARN;
+        } else if (sev_string == "ERROR") {
+            severity = isc::log::ERROR;
+        } else if (sev_string == "FATAL") {
+            severity = isc::log::FATAL;
+        } else {
+            std::cerr << "**ERROR** unrecognised logger severity of '"
+                      << sev_string << "' - default severity will be used\n";
+        }
+    }
+
+    // If the severity is debug, get the debug level (environment variable
+    // B10_LOGGER_DBGLEVEL), which should be in the range 0 to 99.
+    int dbglevel = 0;
+    if (severity == isc::log::DEBUG) {
+        const char* dbg_char = getenv("B10_LOGGER_DBGLEVEL");
+        if (dbg_char) {
+            int level = 0;
+            try {
+                level = boost::lexical_cast<int>(dbg_char);
+                if (level < MIN_DEBUG_LEVEL) {
+                    std::cerr << "**ERROR** debug level of " << level
+                              << " is invalid - a value of " << MIN_DEBUG_LEVEL
+                              << " will be used\n";
+                    level = MIN_DEBUG_LEVEL;
+                } else if (level > MAX_DEBUG_LEVEL) {
+                    std::cerr << "**ERROR** debug level of " << level
+                              << " is invalid - a value of " << MAX_DEBUG_LEVEL
+                              << " will be used\n";
+                    level = MAX_DEBUG_LEVEL;
+                }
+            } catch (...) {
+                // Error, but not fatal to the test
+                std::cerr << "**ERROR** Unable to translate "
+                             "B10_LOGGER_DBGLEVEL - a value of 0 will be used\n";
+            }
+            dbglevel = level;
+        }
+    }
+
+    /// Set the local message file
+    const char* localfile = getenv("B10_LOGGER_LOCALMSG");
+
+    // Initialize logging
+    initLogger(root, severity, dbglevel, localfile);
+}
+
 } // namespace log
 } // namespace isc
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
index 6b5fdec..f4861b2 100644
--- a/src/lib/log/logger_support.h
+++ b/src/lib/log/logger_support.h
@@ -39,6 +39,37 @@ namespace log {
 void initLogger(const std::string& root, isc::log::Severity severity,
     int dbglevel, const char* file);
 
+
+/// \brief Run-Time Initialization from Environment
+///
+/// Performs run-time initialization of the logger via the setting of
+/// environment variables.  These are:
+///
+/// B10_LOGGER_ROOT
+/// Name of the root logger.  If not given, the string "b10root" will be used.
+///
+/// B10_LOGGER_SEVERITY
+/// Severity of messages that will be logged.  This must be one of the strings
+/// "DEBUG", "INFO", "WARN", "ERROR", "FATAL". (Must be upper case and must
+/// not contain leading or trailing spaces.)  If not specified (or if
+/// specified but incorrect), the default for the logging system will be used
+/// (currently INFO).
+///
+/// B10_LOGGER_DBGLEVEL
+/// Ignored if the level is not DEBUG, this should be a number between 0 and
+/// 99 indicating the logging severity.  The default is 0.  If outside these
+/// limits or if not a number, a value of 0 is used.
+///
+/// B10_LOGGER_LOCALMSG
+/// If defined, the path specification of a file that contains message
+/// definitions replacing ones in the default dictionary.
+///
+/// Any errors in the settings cause messages to be output to stderr.
+///
+/// This function is most likely to be called from unit test programs.
+
+void initLogger();
+
 } // namespace log
 } // namespace isc
 
diff --git a/src/lib/log/message_exception.cc b/src/lib/log/message_exception.cc
deleted file mode 100644
index 1a69ca5..0000000
--- a/src/lib/log/message_exception.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-/// \brief Body of Virtual Destructor
-
-#include <log/message_exception.h>
-
-namespace isc {
-namespace log {
-
-MessageException::~MessageException() throw() {
-}
-
-} // namespace log
-} // namespace isc
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
index 30c6618..eebee89 100644
--- a/src/lib/log/message_exception.h
+++ b/src/lib/log/message_exception.h
@@ -19,6 +19,7 @@
 #include <string>
 #include <vector>
 
+#include <boost/lexical_cast.hpp>
 #include <log/message_types.h>
 
 namespace isc {
@@ -35,33 +36,47 @@ public:
 
     /// \brief Constructor
     ///
-    /// \param id Message identification
-    MessageException(MessageID id) : id_(id)
-    {}
+    /// \param id Message identification.
+    /// \param lineno Line number on which error occurred (if > 0).
+    MessageException(MessageID id, int lineno = 0) : id_(id)
+    {
+        if (lineno > 0) {
+            args_.push_back(boost::lexical_cast<std::string>(lineno));
+        }
+    }
 
     /// \brief Constructor
     ///
-    /// \param id Message identification
-    /// \param arg1 First message argument
-    MessageException(MessageID id, const std::string& arg1) : id_(id)
+    /// \param id Message identification.
+    /// \param arg1 First message argument.
+    /// \param lineno Line number on which error occurred (if > 0).
+    MessageException(MessageID id, const std::string& arg1, int lineno = 0)
+        : id_(id)
     {
+        if (lineno > 0) {
+            args_.push_back(boost::lexical_cast<std::string>(lineno));
+        }
         args_.push_back(arg1);
     }
 
     /// \brief Constructor
     ///
-    /// \param id Message identification
-    /// \param arg1 First message argument
-    /// \param arg2 Second message argument
+    /// \param id Message identification.
+    /// \param arg1 First message argument.
+    /// \param arg2 Second message argument.
+    /// \param lineno Line number on which error occurred (if > 0).
     MessageException(MessageID id, const std::string& arg1,
-        const std::string& arg2) : id_(id)
+        const std::string& arg2, int lineno = 0) : id_(id)
     {
+        if (lineno > 0) {
+            args_.push_back(boost::lexical_cast<std::string>(lineno));
+        }
         args_.push_back(arg1);
         args_.push_back(arg2);
     }
 
     /// \brief Destructor
-    virtual ~MessageException() throw();
+    ~MessageException() throw() {}
 
     /// \brief Return Message ID
     ///
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
index 7281346..1a0b242 100644
--- a/src/lib/log/message_reader.cc
+++ b/src/lib/log/message_reader.cc
@@ -12,8 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <cassert>
 #include <errno.h>
 #include <string.h>
+#include <iostream>
 
 #include <iostream>
 #include <fstream>
@@ -25,45 +27,50 @@
 
 using namespace std;
 
-namespace isc {
-namespace log {
-
-// Virtual destructor.
-MessageReader::~MessageReader() {
+namespace {
+const char DIRECTIVE_FLAG = '$';    // Starts each directive
+const char MESSAGE_FLAG = '%';      // Starts each message
 }
 
 
+namespace isc {
+namespace log {
+
 // Read the file.
 
 void
 MessageReader::readFile(const string& file, MessageReader::Mode mode) {
 
-    // Ensure the non-added collection is empty: this object might be
-    // being reused.
+    // Ensure the non-added collection is empty: we could be re-using this
+    // object.
     not_added_.clear();
 
-    // Open the file
+    // Open the file.
     ifstream infile(file.c_str());
     if (infile.fail()) {
-        throw MessageException(MSG_OPNMSGIN, file, strerror(errno));
+        throw MessageException(MSG_OPENIN, file, strerror(errno));
     }
 
-    // Loop round reading it.
+    // Loop round reading it.  As we process the file one line at a time,
+    // keep a track of line number of aid diagnosis of problems.
     string line;
     getline(infile, line);
+    lineno_ = 0;
+
     while (infile.good()) {
+        ++lineno_;
         processLine(line, mode);
         getline(infile, line);
     }
 
     // Why did the loop terminate?
     if (!infile.eof()) {
-        throw MessageException(MSG_MSGRDERR, file, strerror(errno));
+        throw MessageException(MSG_READERR, file, strerror(errno));
     }
     infile.close();
 }
 
-// Parse a line of the file
+// Parse a line of the file.
 
 void
 MessageReader::processLine(const string& line, MessageReader::Mode mode) {
@@ -74,15 +81,16 @@ MessageReader::processLine(const string& line, MessageReader::Mode mode) {
     if (text.empty()) {
         ;                           // Ignore blank lines
 
-    } else if ((text[0] == '#') || (text[0] == '+')) {
-        ;                           // Ignore comments or descriptions
-
-    } else if (text[0] == '$') {
+    } else if (text[0] == DIRECTIVE_FLAG) {
         parseDirective(text);       // Process directives
 
-    } else {
-        parseMessage(text, mode);   // Process other lines
 
+    } else if (text[0] == MESSAGE_FLAG) {
+        parseMessage(text, mode);   // Process message definition line
+
+    } else {
+        ;                           // Other lines are extended message
+                                    // description so are ignored
     }
 }
 
@@ -99,130 +107,162 @@ MessageReader::parseDirective(const std::string& text) {
     isc::util::str::uppercase(tokens[0]);
     if (tokens[0] == string("$PREFIX")) {
         parsePrefix(tokens);
+
     } else if (tokens[0] == string("$NAMESPACE")) {
         parseNamespace(tokens);
+
     } else {
-        throw MessageException(MSG_UNRECDIR, tokens[0]);
+
+        // Unrecognised directive
+        throw MessageException(MSG_UNRECDIR, tokens[0], lineno_);
     }
 }
 
 // Process $PREFIX
-
 void
 MessageReader::parsePrefix(const vector<string>& tokens) {
 
-    // Check argument count
-
-    static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
-    if (tokens.size() < 2) {
-        throw MessageException(MSG_PRFNOARG);
-    } else if (tokens.size() > 2) {
-        throw MessageException(MSG_PRFEXTRARG);
-
-    }
+    // Should not get here unless there is something in the tokens array.
+    assert(tokens.size() > 0);
 
-    // As a style, we are going to have the symbols in uppercase
-    string prefix = tokens[1];
-    isc::util::str::uppercase(prefix);
+    // Process $PREFIX.  With no arguments, the prefix is set to the empty
+    // string.  One argument sets the prefix to the to its value and more than
+    // one argument is invalid.
+    if (tokens.size() == 1) {
+        prefix_ = "";
 
-    // Token is potentially valid providing it only contains alphabetic
-    // and numeric characters (and underscores) and does not start with a
-    // digit.
-    if ((prefix.find_first_not_of(valid) != string::npos) ||
-        (std::isdigit(prefix[0]))) {
+    } else if (tokens.size() == 2) {
+        prefix_ = tokens[1];
 
-        // Invalid character in string or it starts with a digit.
-        throw MessageException(MSG_PRFINVARG, tokens[1]);
-    }
+        // Token is potentially valid providing it only contains alphabetic
+        // and numeric characters (and underscores) and does not start with a
+        // digit.
+        if (invalidSymbol(prefix_)) {
+            throw MessageException(MSG_PRFINVARG, prefix_, lineno_);
+        }
 
-    // All OK - unless the prefix has already been set.
+    } else {
 
-    if (prefix_.size() != 0) {
-        throw MessageException(MSG_DUPLPRFX);
+        // Too many arguments
+        throw MessageException(MSG_PRFEXTRARG, lineno_);
     }
+}
 
-    // Prefix has not been set, so set it and return success.
-
-    prefix_ = prefix;
+// Check if string is an invalid C++ symbol.  It is valid if comprises only
+// alphanumeric characters and underscores, and does not start with a digit.
+// (Owing to the logic of the rest of the code, we check for its invalidity,
+// not its validity.)
+bool
+MessageReader::invalidSymbol(const string& symbol) {
+    static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                      "abcdefghijklmnopqrstuvwxyz"
+                                      "0123456789_";
+    return ( symbol.empty() ||
+            (symbol.find_first_not_of(valid_chars) != string::npos) ||
+            (std::isdigit(symbol[0])));
 }
 
 // Process $NAMESPACE.  A lot of the processing is similar to that of $PREFIX,
 // except that only limited checks will be done on the namespace (to avoid a
-// lot of parsing and separating out of the namespace components.)
+// lot of parsing and separating out of the namespace components.)  Also, unlike
+// $PREFIX, there can only be one $NAMESPACE in a file.
 
 void
 MessageReader::parseNamespace(const vector<string>& tokens) {
 
     // Check argument count
-
-    static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:"
-        "abcdefghijklmnopqrstuvwxyz";
-
     if (tokens.size() < 2) {
-        throw MessageException(MSG_NSNOARG);
+        throw MessageException(MSG_NSNOARG, lineno_);
 
     } else if (tokens.size() > 2) {
-        throw MessageException(MSG_NSEXTRARG);
+        throw MessageException(MSG_NSEXTRARG, lineno_);
 
     }
 
     // Token is potentially valid providing it only contains alphabetic
-    // and numeric characters (and underscores and colons).
-    if (tokens[1].find_first_not_of(valid) != string::npos) {
-
-        // Invalid character in string or it starts with a digit.
-        throw MessageException(MSG_NSINVARG, tokens[1]);
+    // and numeric characters (and underscores and colons).  As noted above,
+    // we won't be exhaustive - after all, and code containing the resultant
+    // namespace will have to be compiled, and the compiler will catch errors.
+    static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                      "abcdefghijklmnopqrstuvwxyz"
+                                      "0123456789_:";
+    if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
+        throw MessageException(MSG_NSINVARG, tokens[1], lineno_);
     }
 
     // All OK - unless the namespace has already been set.
     if (ns_.size() != 0) {
-        throw MessageException(MSG_DUPLNS);
+        throw MessageException(MSG_DUPLNS, lineno_);
     }
 
     // Prefix has not been set, so set it and return success.
-
     ns_ = tokens[1];
 }
 
 // Process message.  By the time this method is called, the line has been
-// stripped of leading and trailing spaces, and we believe that it is a line
-// defining a message.  The first token on the line is converted to uppercase
-// and becomes the message ID; the rest of the line is the message text.
+// stripped of leading and trailing spaces.  The first character of the string
+// is the message introducer, so we can get rid of that.  The remainder is
+// a line defining a message.
+//
+// The first token on the line, when concatenated to the prefix and converted to
+// upper-case, is the message ID.  The first of the line from the next token
+// on is the message text.
 
 void
 MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
 
     static string delimiters("\t\n ");   // Delimiters
 
+    // The line passed should be at least one character long and start with the
+    // message introducer (else we should not have got here).
+    assert((text.size() >= 1) && (text[0] == MESSAGE_FLAG));
+
+    // A line comprising just the message introducer is not valid.
+    if (text.size() == 1) {
+        throw MessageException(MSG_NOMSGID, text, lineno_);
+    }
+
+    // Strip off the introducer and any leading space after that.
+    string message_line = isc::util::str::trim(text.substr(1));
+
     // Look for the first delimiter.
-    size_t first_delim = text.find_first_of(delimiters);
+    size_t first_delim = message_line.find_first_of(delimiters);
     if (first_delim == string::npos) {
 
         // Just a single token in the line - this is not valid
-        throw MessageException(MSG_NOMSGTXT, text);
+        throw MessageException(MSG_NOMSGTXT, message_line, lineno_);
     }
 
-    // Extract the first token into the message ID
-    string ident = text.substr(0, first_delim);
+    // Extract the first token into the message ID, preceding it with the
+    // current prefix, then convert to upper-case.  If the prefix is not set,
+    // perform the valid character check now - the string will become a C++
+    // symbol so we may as well identify problems early.
+    string ident = prefix_ + message_line.substr(0, first_delim);
+    if (prefix_.empty()) {
+        if (invalidSymbol(ident)) {
+            throw MessageException(MSG_INVMSGID, ident, lineno_);
+        }
+    }
+    isc::util::str::uppercase(ident);
 
     // Locate the start of the message text
-    size_t first_text = text.find_first_not_of(delimiters, first_delim);
+    size_t first_text = message_line.find_first_not_of(delimiters, first_delim);
     if (first_text == string::npos) {
 
         // ?? This happens if there are trailing delimiters, which should not
         // occur as we have stripped trailing spaces off the line.  Just treat
         // this as a single-token error for simplicity's sake.
-        throw MessageException(MSG_NOMSGTXT, text);
+        throw MessageException(MSG_NOMSGTXT, message_line, lineno_);
     }
 
     // Add the result to the dictionary and to the non-added list if the add to
     // the dictionary fails.
     bool added;
     if (mode == ADD) {
-        added = dictionary_->add(ident, text.substr(first_text));
+        added = dictionary_->add(ident, message_line.substr(first_text));
     }
     else {
-        added = dictionary_->replace(ident, text.substr(first_text));
+        added = dictionary_->replace(ident, message_line.substr(first_text));
     }
     if (!added) {
         not_added_.push_back(ident);
diff --git a/src/lib/log/message_reader.h b/src/lib/log/message_reader.h
index d07c7f2..eded9c6 100644
--- a/src/lib/log/message_reader.h
+++ b/src/lib/log/message_reader.h
@@ -64,10 +64,9 @@ public:
         dictionary_(dictionary)
     {}
 
-
     /// \brief Virtual Destructor
-    virtual ~MessageReader();
-
+    virtual ~MessageReader()
+    {}
 
     /// \brief Get Dictionary
     ///
@@ -188,10 +187,24 @@ private:
     /// \param tokens $NAMESPACE line split into tokens
     void parseNamespace(const std::vector<std::string>& tokens);
 
+    /// \brief Check for invalid C++ symbol name
+    ///
+    /// The message ID (or concatenation of prefix and message ID) will be used
+    /// as the name of a symbol in C++ code.  This function checks if the name
+    /// is invalid (contains anything other than alphanumeric characters or
+    /// underscores, or starts with a digit).
+    ///
+    /// \param symbol name to check to see if it is an invalid C++ symbol.
+    ///
+    /// \return true if the name is invalid, false if it is valid.
+    bool invalidSymbol(const std::string& symbol);
+
+
 
     /// Attributes
     MessageDictionary*  dictionary_;    ///< Dictionary to add messages to
     MessageIDCollection not_added_;     ///< List of IDs not added
+    int                 lineno_;        ///< Number of last line read
     std::string         prefix_;        ///< Argument of $PREFIX statement
     std::string         ns_;            ///< Argument of $NAMESPACE statement
 };
diff --git a/src/lib/log/messagedef.cc b/src/lib/log/messagedef.cc
index 46086d0..5cc89b3 100644
--- a/src/lib/log/messagedef.cc
+++ b/src/lib/log/messagedef.cc
@@ -1,4 +1,4 @@
-// File created from messagedef.mes on Thu May  5 16:57:11 2011
+// File created from messagedef.mes on Mon May  9 13:52:54 2011
 
 #include <cstddef>
 #include <log/message_types.h>
@@ -7,23 +7,23 @@
 namespace isc {
 namespace log {
 
-extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS";
-extern const isc::log::MessageID MSG_DUPLPRFX = "DUPLPRFX";
-extern const isc::log::MessageID MSG_DUPMSGID = "DUPMSGID";
-extern const isc::log::MessageID MSG_IDNOTFND = "IDNOTFND";
-extern const isc::log::MessageID MSG_MSGRDERR = "MSGRDERR";
-extern const isc::log::MessageID MSG_MSGWRTERR = "MSGWRTERR";
-extern const isc::log::MessageID MSG_NOMSGTXT = "NOMSGTXT";
-extern const isc::log::MessageID MSG_NSEXTRARG = "NSEXTRARG";
-extern const isc::log::MessageID MSG_NSINVARG = "NSINVARG";
-extern const isc::log::MessageID MSG_NSNOARG = "NSNOARG";
-extern const isc::log::MessageID MSG_OPNMSGIN = "OPNMSGIN";
-extern const isc::log::MessageID MSG_OPNMSGOUT = "OPNMSGOUT";
-extern const isc::log::MessageID MSG_PRFEXTRARG = "PRFEXTRARG";
-extern const isc::log::MessageID MSG_PRFINVARG = "PRFINVARG";
-extern const isc::log::MessageID MSG_PRFNOARG = "PRFNOARG";
-extern const isc::log::MessageID MSG_RDLOCMES = "RDLOCMES";
-extern const isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
+extern const isc::log::MessageID MSG_DUPLNS = "MSG_DUPLNS";
+extern const isc::log::MessageID MSG_DUPMSGID = "MSG_DUPMSGID";
+extern const isc::log::MessageID MSG_IDNOTFND = "MSG_IDNOTFND";
+extern const isc::log::MessageID MSG_INVMSGID = "MSG_INVMSGID";
+extern const isc::log::MessageID MSG_NOMSGID = "MSG_NOMSGID";
+extern const isc::log::MessageID MSG_NOMSGTXT = "MSG_NOMSGTXT";
+extern const isc::log::MessageID MSG_NSEXTRARG = "MSG_NSEXTRARG";
+extern const isc::log::MessageID MSG_NSINVARG = "MSG_NSINVARG";
+extern const isc::log::MessageID MSG_NSNOARG = "MSG_NSNOARG";
+extern const isc::log::MessageID MSG_OPENIN = "MSG_OPENIN";
+extern const isc::log::MessageID MSG_OPENOUT = "MSG_OPENOUT";
+extern const isc::log::MessageID MSG_PRFEXTRARG = "MSG_PRFEXTRARG";
+extern const isc::log::MessageID MSG_PRFINVARG = "MSG_PRFINVARG";
+extern const isc::log::MessageID MSG_RDLOCMES = "MSG_RDLOCMES";
+extern const isc::log::MessageID MSG_READERR = "MSG_READERR";
+extern const isc::log::MessageID MSG_UNRECDIR = "MSG_UNRECDIR";
+extern const isc::log::MessageID MSG_WRITERR = "MSG_WRITERR";
 
 } // namespace log
 } // namespace isc
@@ -31,23 +31,23 @@ extern const isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
 namespace {
 
 const char* values[] = {
-    "DUPLNS", "duplicate $NAMESPACE directive found",
-    "DUPLPRFX", "duplicate $PREFIX directive found",
-    "DUPMSGID", "duplicate message ID (%1) in compiled code",
-    "IDNOTFND", "could not replace message for '%1': no such message identification",
-    "MSGRDERR", "error reading from message file %1: %2",
-    "MSGWRTERR", "error writing to %1: %2",
-    "NOMSGTXT", "a line containing a message ID ('%1') and nothing else was found",
-    "NSEXTRARG", "$NAMESPACE directive has too many arguments",
-    "NSINVARG", "$NAMESPACE directive has an invalid argument ('%1')",
-    "NSNOARG", "no arguments were given to the $NAMESPACE directive",
-    "OPNMSGIN", "unable to open message file %1 for input: %2",
-    "OPNMSGOUT", "unable to open %1 for output: %2",
-    "PRFEXTRARG", "$PREFIX directive has too many arguments",
-    "PRFINVARG", "$PREFIX directive has an invalid argument ('%1')",
-    "PRFNOARG", "no arguments were given to the $PREFIX directive",
-    "RDLOCMES", "reading local message file %1",
-    "UNRECDIR", "unrecognised directive '%1'",
+    "MSG_DUPLNS", "line %1: duplicate $NAMESPACE directive found",
+    "MSG_DUPMSGID", "duplicate message ID (%1) in compiled code",
+    "MSG_IDNOTFND", "could not replace message text for '%1': no such message",
+    "MSG_INVMSGID", "line %1: invalid message identification '%2'",
+    "MSG_NOMSGID", "line %1: message definition line found without a message ID",
+    "MSG_NOMSGTXT", "line %1: line found containing a message ID ('%2') and no text",
+    "MSG_NSEXTRARG", "line %1: $NAMESPACE directive has too many arguments",
+    "MSG_NSINVARG", "line %1: $NAMESPACE directive has an invalid argument ('%2')",
+    "MSG_NSNOARG", "line %1: no arguments were given to the $NAMESPACE directive",
+    "MSG_OPENIN", "unable to open message file %1 for input: %2",
+    "MSG_OPENOUT", "unable to open %1 for output: %2",
+    "MSG_PRFEXTRARG", "line %1: $PREFIX directive has too many arguments",
+    "MSG_PRFINVARG", "line %1: $PREFIX directive has an invalid argument ('%2')",
+    "MSG_RDLOCMES", "reading local message file %1",
+    "MSG_READERR", "error reading from message file %1: %2",
+    "MSG_UNRECDIR", "line %1: unrecognised directive '%2'",
+    "MSG_WRITERR", "error writing to %1: %2",
     NULL
 };
 
diff --git a/src/lib/log/messagedef.h b/src/lib/log/messagedef.h
index 47867e6..79c8bab 100644
--- a/src/lib/log/messagedef.h
+++ b/src/lib/log/messagedef.h
@@ -1,4 +1,4 @@
-// File created from messagedef.mes on Thu May  5 16:57:11 2011
+// File created from messagedef.mes on Mon May  9 13:52:54 2011
 
 #ifndef __MESSAGEDEF_H
 #define __MESSAGEDEF_H
@@ -9,22 +9,22 @@ namespace isc {
 namespace log {
 
 extern const isc::log::MessageID MSG_DUPLNS;
-extern const isc::log::MessageID MSG_DUPLPRFX;
 extern const isc::log::MessageID MSG_DUPMSGID;
 extern const isc::log::MessageID MSG_IDNOTFND;
-extern const isc::log::MessageID MSG_MSGRDERR;
-extern const isc::log::MessageID MSG_MSGWRTERR;
+extern const isc::log::MessageID MSG_INVMSGID;
+extern const isc::log::MessageID MSG_NOMSGID;
 extern const isc::log::MessageID MSG_NOMSGTXT;
 extern const isc::log::MessageID MSG_NSEXTRARG;
 extern const isc::log::MessageID MSG_NSINVARG;
 extern const isc::log::MessageID MSG_NSNOARG;
-extern const isc::log::MessageID MSG_OPNMSGIN;
-extern const isc::log::MessageID MSG_OPNMSGOUT;
+extern const isc::log::MessageID MSG_OPENIN;
+extern const isc::log::MessageID MSG_OPENOUT;
 extern const isc::log::MessageID MSG_PRFEXTRARG;
 extern const isc::log::MessageID MSG_PRFINVARG;
-extern const isc::log::MessageID MSG_PRFNOARG;
 extern const isc::log::MessageID MSG_RDLOCMES;
+extern const isc::log::MessageID MSG_READERR;
 extern const isc::log::MessageID MSG_UNRECDIR;
+extern const isc::log::MessageID MSG_WRITERR;
 
 } // namespace log
 } // namespace isc
diff --git a/src/lib/log/messagedef.mes b/src/lib/log/messagedef.mes
index 077830c..51c04fa 100644
--- a/src/lib/log/messagedef.mes
+++ b/src/lib/log/messagedef.mes
@@ -12,108 +12,108 @@
 # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-$PREFIX MSG_
-$NAMESPACE isc::log
-
 # \brief Message Utility Message File
 #
-# This is the source of the set of messages generated by the message and logging
-# components.  The associated .h and .cc files are created by hand from this
-# file though and are not built during the build process; this is to avoid the
-# chicken-and-egg situation where we need the files to build the message
+# This is the source of the set of messages generated by the message and
+# logging components.  The associated .h and .cc files are created by hand from
+# this file though and are not built during the build process; this is to avoid
+# the chicken-and-egg situation where we need the files to build the message
 # compiler, yet we need the compiler to build the files.
 
-DUPMSGID  duplicate message ID (%1) in compiled code
-+ Indicative of a programming error, when it started up, BIND10 detected that
-+ the given message ID had been registered by one or more modules.  (All message
-+ IDs should be unique throughout BIND10.)  This has no impact on the operation
-+ of the server other that erroneous messages may be logged.  (When BIND10 loads
-+ the message IDs (and their associated text), if a duplicate ID is found it is
-+ discarded.  However, when the module that supplied the duplicate ID logs that
-+ particular message, the text supplied by the module that added the original
-+ ID will be output - something that may bear no relation to the condition being
-+ logged.
-
-DUPLNS    duplicate $NAMESPACE directive found
-+ When reading a message file, more than one $NAMESPACE directive was found.  In
-+ this version of the code, such a condition is regarded as an error and the
-+ read will be abandoned.
-
-DUPLPRFX    duplicate $PREFIX directive found
-+ When reading a message file, more than one $PREFIX directive was found.  In
-+ this version of the code, such a condition is regarded as an error and the
-+ read will be abandoned.
-
-IDNOTFND    could not replace message for '%1': no such message identification
-+ During start-up a local message file was read.  A line with the listed
-+ message identification was found in the file, but the identification is not
-+ one contained in the compiled-in message dictionary.  Either the message
-+ identification has been mis-spelled in the file, or the local file was used
-+ for an earlier version of the software and the message with that
-+ identification has been removed.
-+
-+ This message may appear a number of times in the file, once for every such
-+ unknown message identification.
-
-MSGRDERR    error reading from message file %1: %2
-+ The specified error was encountered reading from the named message file.
-
-MSGWRTERR   error writing to %1: %2
-+ The specified error was encountered by the message compiler when writing to
-+ the named output file.
-
-NSEXTRARG  $NAMESPACE directive has too many arguments
-+ The $NAMESPACE directive takes a single argument, a namespace in which all the
-+ generated symbol names are placed.  This error is generated when the
-+ compiler finds a $NAMESPACE directive with more than one argument.
-
-NSINVARG    $NAMESPACE directive has an invalid argument ('%1')
-+ The $NAMESPACE argument should be a valid C++ namespace.  The reader does a
-+ cursory check on its validity, checking that the characters in the namespace
-+ are correct.  The error is generated when the reader finds an invalid
-+ character. (Valid are alphanumeric characters, underscores and colons.)
-
-NOMSGTXT    a line containing a message ID ('%1') and nothing else was found
-+ Message definitions comprise lines starting with a message identification (a
-+ symbolic name for the message) and followed by the text of the message.  This
-+ error is generated when a line is found in the message file that contains just
-+ the message identification and no text.
-
-NSNOARG     no arguments were given to the $NAMESPACE directive
-+ The $NAMESPACE directive takes a single argument, a namespace in which all the
-+ generated symbol names are placed.  This error is generated when the
-+ compiler finds a $NAMESPACE directive with no arguments.
-
-OPNMSGIN     unable to open message file %1 for input: %2
-+ The program was not able to open the specified input message file for the
-+ reason given.
-
-OPNMSGOUT   unable to open %1 for output: %2
-+ The program was not able to open the specified output file for the reason
-+ given.
-
-PRFEXTRARG  $PREFIX directive has too many arguments
-+ The $PREFIX directive takes a single argument, a prefix to be added to the
-+ symbol names when a C++ .h file is created.  This error is generated when the
-+ compiler finds a $PREFIX directive with more than one argument.
-
-PRFINVARG   $PREFIX directive has an invalid argument ('%1')
-+ The $PREFIX argument is used in a symbol name in a C++ header file.  As such,
-+ it must adhere to restrictions on C++ symbol names (e.g. may only contain
-+ alphanumeric characters or underscores, and may nor start with a digit).  A
-+ $PREFIX directive was found with an argument (given in the message) that
-+ violates those restictions.
-
-PRFNOARG    no arguments were given to the $PREFIX directive
-+ The $PREFIX directive takes a single argument, a prefix to be added to the
-+ symbol names when a C++ .h file is created.  This error is generated when the
-+ compiler finds a $PREFIX directive with no arguments.
-
-RDLOCMES    reading local message file %1
-+ This is an informational message output by BIND10 when it starts to read a
-+ local message file.  (A local message file may replace the text of one of more
-+ messages; the ID of the message will not be changed though.)
-
-UNRECDIR    unrecognised directive '%1'
-+ A line starting with a dollar symbol was found, but the first word on the line
-+ (shown in the message) was not a recognised message compiler directive.
+$PREFIX MSG_
+$NAMESPACE isc::log
+
+% DUPMSGID      duplicate message ID (%1) in compiled code
+Indicative of a programming error, when it started up, BIND10 detected that
+the given message ID had been registered by one or more modules.  (All message
+IDs should be unique throughout BIND10.)  This has no impact on the operation
+of the server other that erroneous messages may be logged.  (When BIND10 loads
+the message IDs (and their associated text), if a duplicate ID is found it is
+discarded.  However, when the module that supplied the duplicate ID logs that
+particular message, the text supplied by the module that added the original
+ID will be output - something that may bear no relation to the condition being
+logged.
+
+% DUPLNS        line %1: duplicate $NAMESPACE directive found
+When reading a message file, more than one $NAMESPACE directive was found.  In
+this version of the code, such a condition is regarded as an error and the
+read will be abandoned.
+
+% IDNOTFND      could not replace message text for '%1': no such message
+During start-up a local message file was read.  A line with the listed
+message identification was found in the file, but the identification is not
+one contained in the compiled-in message dictionary.  Either the message
+identification has been mis-spelled in the file, or the local file was used
+for an earlier version of the software and the message with that
+identification has been removed.
+
+This message may appear a number of times in the file, once for every such
+unknown message identification.
+
+% INVMSGID      line %1: invalid message identification '%2'
+The concatenation of the prefix and the message identification is used as
+a symbol in the C++ module; as such it may only contain 
+
+% NOMSGID       line %1: message definition line found without a message ID
+Message definition lines are lines starting with a "%".  The rest of the line
+should comprise the message ID and text describing the message.  This error
+indicates the message compiler found a line in the message file comprising
+just the "%" and nothing else.
+
+% NOMSGTXT      line %1: line found containing a message ID ('%2') and no text
+Message definition lines are lines starting with a "%".  The rest of the line
+should comprise the message ID and text describing the message.  This error
+is generated when a line is found in the message file that contains the
+leading "%" and the message identification but no text.
+
+% NSEXTRARG     line %1: $NAMESPACE directive has too many arguments
+The $NAMESPACE directive takes a single argument, a namespace in which all the
+generated symbol names are placed.  This error is generated when the
+compiler finds a $NAMESPACE directive with more than one argument.
+
+% NSINVARG      line %1: $NAMESPACE directive has an invalid argument ('%2')
+The $NAMESPACE argument should be a valid C++ namespace.  The reader does a
+cursory check on its validity, checking that the characters in the namespace
+are correct.  The error is generated when the reader finds an invalid
+character. (Valid are alphanumeric characters, underscores and colons.)
+
+% NSNOARG       line %1: no arguments were given to the $NAMESPACE directive
+The $NAMESPACE directive takes a single argument, a namespace in which all the
+generated symbol names are placed.  This error is generated when the
+compiler finds a $NAMESPACE directive with no arguments.
+
+% OPENIN        unable to open message file %1 for input: %2
+The program was not able to open the specified input message file for the
+reason given.
+
+% OPENOUT       unable to open %1 for output: %2
+The program was not able to open the specified output file for the reason
+given.
+
+% PRFEXTRARG    line %1: $PREFIX directive has too many arguments
+The $PREFIX directive takes a single argument, a prefix to be added to the
+symbol names when a C++ .h file is created.  This error is generated when the
+compiler finds a $PREFIX directive with more than one argument.
+
+% PRFINVARG     line %1: $PREFIX directive has an invalid argument ('%2')
+The $PREFIX argument is used in a symbol name in a C++ header file.  As such,
+it must adhere to restrictions on C++ symbol names (e.g. may only contain
+alphanumeric characters or underscores, and may nor start with a digit).
+A $PREFIX directive was found with an argument (given in the message) that
+violates those restictions.
+
+% RDLOCMES      reading local message file %1
+This is an informational message output by BIND10 when it starts to read a
+local message file.  (A local message file may replace the text of one of more
+messages; the ID of the message will not be changed though.)
+
+% READERR       error reading from message file %1: %2
+The specified error was encountered reading from the named message file.
+
+% WRITERR       error writing to %1: %2
+The specified error was encountered by the message compiler when writing to
+the named output file.
+
+% UNRECDIR      line %1: unrecognised directive '%2'
+A line starting with a dollar symbol was found, but the first word on the line
+(shown in the message) was not a recognised message compiler directive.
diff --git a/src/lib/log/tests/logger_support_test.cc b/src/lib/log/tests/logger_support_test.cc
index d0d5c53..0a2338b 100644
--- a/src/lib/log/tests/logger_support_test.cc
+++ b/src/lib/log/tests/logger_support_test.cc
@@ -93,13 +93,14 @@ int main(int argc, char** argv) {
     initLogger("alpha", severity, dbglevel, localfile);
 
     // Log a few messages
-    LOG_FATAL(logger_ex, MSG_MSGWRTERR).arg("test1").arg("42");
-    LOG_ERROR(logger_ex, MSG_UNRECDIR).arg("false");
-    LOG_WARN(logger_dlm, MSG_MSGRDERR).arg("a.txt").arg("dummy test");
-    LOG_INFO(logger_dlm, MSG_OPNMSGIN).arg("example.msg").arg("dummy test");
-    LOG_DEBUG(logger_ex, 0, MSG_UNRECDIR).arg("[abc]");
-    LOG_DEBUG(logger_ex, 24, MSG_UNRECDIR).arg("[24]");
-    LOG_DEBUG(logger_ex, 25, MSG_UNRECDIR).arg("[25]");
-    LOG_DEBUG(logger_ex, 26, MSG_UNRECDIR).arg("[26]");
+    LOG_FATAL(logger_ex, MSG_WRITERR).arg("test1").arg("42");
+    LOG_ERROR(logger_ex, MSG_RDLOCMES).arg("dummy/file");
+    LOG_WARN(logger_dlm, MSG_READERR).arg("a.txt").arg("dummy reason");
+    LOG_INFO(logger_dlm, MSG_OPENIN).arg("example.msg").arg("dummy reason");
+    LOG_DEBUG(logger_ex, 0, MSG_RDLOCMES).arg("dummy/0");
+    LOG_DEBUG(logger_ex, 24, MSG_RDLOCMES).arg("dummy/24");
+    LOG_DEBUG(logger_ex, 25, MSG_RDLOCMES).arg("dummy/25");
+    LOG_DEBUG(logger_ex, 26, MSG_RDLOCMES).arg("dummy/26");
+
     return (0);
 }
diff --git a/src/lib/log/tests/message_dictionary_unittest.cc b/src/lib/log/tests/message_dictionary_unittest.cc
index a92585c..ba33820 100644
--- a/src/lib/log/tests/message_dictionary_unittest.cc
+++ b/src/lib/log/tests/message_dictionary_unittest.cc
@@ -29,7 +29,7 @@ using namespace std;
 // and the latter should be present.
 
 static const char* values[] = {
-    "DUPLNS", "duplicate $NAMESPACE directive found",
+    "MSG_DUPLNS", "duplicate $NAMESPACE directive found",
     "NEWSYM", "new symbol added",
     NULL
 };
@@ -190,7 +190,7 @@ TEST_F(MessageDictionaryTest, GlobalTest) {
 TEST_F(MessageDictionaryTest, GlobalLoadTest) {
     vector<string>& duplicates = MessageInitializer::getDuplicates();
     ASSERT_EQ(1, duplicates.size());
-    EXPECT_EQ(string("DUPLNS"), duplicates[0]);
+    EXPECT_EQ(string("MSG_DUPLNS"), duplicates[0]);
 
     string text = MessageDictionary::globalDictionary().getText("NEWSYM");
     EXPECT_EQ(string("new symbol added"), text);
diff --git a/src/lib/log/tests/message_reader_unittest.cc b/src/lib/log/tests/message_reader_unittest.cc
index 36288f2..7b3ba5f 100644
--- a/src/lib/log/tests/message_reader_unittest.cc
+++ b/src/lib/log/tests/message_reader_unittest.cc
@@ -68,8 +68,8 @@ TEST_F(MessageReaderTest, BlanksAndComments) {
     EXPECT_NO_THROW(reader_.processLine(" \n "));
     EXPECT_NO_THROW(reader_.processLine("# This is a comment"));
     EXPECT_NO_THROW(reader_.processLine("\t\t # Another comment"));
-    EXPECT_NO_THROW(reader_.processLine("  + A description line"));
-    EXPECT_NO_THROW(reader_.processLine("#+ A comment"));
+    EXPECT_NO_THROW(reader_.processLine("  A description line"));
+    EXPECT_NO_THROW(reader_.processLine("# A comment"));
     EXPECT_NO_THROW(reader_.processLine("  +# A description line"));
 
     // ... and (b) nothing gets added to either the map or the not-added section.
@@ -97,6 +97,15 @@ processLineException(MessageReader& reader, const char* what,
     }
 }
 
+// Check that it recognises invalid directives
+
+TEST_F(MessageReaderTest, InvalidDirectives) {
+
+    // Check that a "$" with nothing else generates an error
+    processLineException(reader_, "$", MSG_UNRECDIR);
+    processLineException(reader_, "$xyz", MSG_UNRECDIR);
+}
+
 // Check that it can parse a prefix
 
 TEST_F(MessageReaderTest, Prefix) {
@@ -104,8 +113,8 @@ TEST_F(MessageReaderTest, Prefix) {
     // Check that no $PREFIX is present
     EXPECT_EQ(string(""), reader_.getPrefix());
 
-    // Check that a $PREFIX directive with no argument generates an error.
-    processLineException(reader_, "$PREFIX", MSG_PRFNOARG);
+    // Check that a $PREFIX directive with no argument is OK
+    EXPECT_NO_THROW(reader_.processLine("$PREFIX"));
 
     // Check a $PREFIX with multiple arguments is invalid
     processLineException(reader_, "$prefix A B", MSG_PRFEXTRARG);
@@ -118,17 +127,19 @@ TEST_F(MessageReaderTest, Prefix) {
 
     // A valid prefix should be accepted
     EXPECT_NO_THROW(reader_.processLine("$PREFIX   dlm__"));
-    EXPECT_EQ(string("DLM__"), reader_.getPrefix());
+    EXPECT_EQ(string("dlm__"), reader_.getPrefix());
 
     // And check that the parser fails on invalid prefixes...
     processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
 
-    // ... and rejects another valid one
-    processLineException(reader_, "$PREFIX ABC", MSG_DUPLPRFX);
-
     // Check that we can clear the prefix as well
     reader_.clearPrefix();
     EXPECT_EQ(string(""), reader_.getPrefix());
+
+    EXPECT_NO_THROW(reader_.processLine("$PREFIX   dlm__"));
+    EXPECT_EQ(string("dlm__"), reader_.getPrefix());
+    EXPECT_NO_THROW(reader_.processLine("$PREFIX"));
+    EXPECT_EQ(string(""), reader_.getPrefix());
 }
 
 // Check that it can parse a namespace
@@ -173,8 +184,8 @@ TEST_F(MessageReaderTest, Namespace) {
 TEST_F(MessageReaderTest, ValidMessageAddDefault) {
 
     // Add a couple of valid messages
-    reader_.processLine("GLOBAL1\t\tthis is message global one\n");
-    reader_.processLine("GLOBAL2 this is message global two");
+    reader_.processLine("% GLOBAL1\t\tthis is message global one\n");
+    reader_.processLine("%GLOBAL2 this is message global two");
 
     // ... and check them
     EXPECT_EQ(string("this is message global one"),
@@ -191,9 +202,9 @@ TEST_F(MessageReaderTest, ValidMessageAddDefault) {
 TEST_F(MessageReaderTest, ValidMessageAdd) {
 
     // Add a couple of valid messages
-    reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+    reader_.processLine("%GLOBAL1\t\tthis is message global one\n",
         MessageReader::ADD);
-    reader_.processLine("GLOBAL2 this is message global two",
+    reader_.processLine("% GLOBAL2 this is message global two",
         MessageReader::ADD);
 
     // ... and check them
@@ -214,9 +225,9 @@ TEST_F(MessageReaderTest, ValidMessageReplace) {
     dictionary_->add("GLOBAL2", "original global2 message");
 
     // Replace a couple of valid messages
-    reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+    reader_.processLine("% GLOBAL1\t\tthis is message global one\n",
         MessageReader::REPLACE);
-    reader_.processLine("GLOBAL2 this is message global two",
+    reader_.processLine("% GLOBAL2 this is message global two",
         MessageReader::REPLACE);
 
     // ... and check them
@@ -237,14 +248,14 @@ TEST_F(MessageReaderTest, ValidMessageReplace) {
 TEST_F(MessageReaderTest, Overflows) {
 
     // Add a couple of valid messages
-    reader_.processLine("GLOBAL1\t\tthis is message global one\n");
-    reader_.processLine("GLOBAL2 this is message global two");
+    reader_.processLine("% GLOBAL1\t\tthis is message global one\n");
+    reader_.processLine("% GLOBAL2 this is message global two");
 
     // Add a duplicate in ADD mode.
-    reader_.processLine("GLOBAL1\t\tthis is a replacement for global one");
+    reader_.processLine("% GLOBAL1\t\tthis is a replacement for global one");
 
     // Replace a non-existent one in REPLACE mode
-    reader_.processLine("LOCAL\t\tthis is a new message",
+    reader_.processLine("% LOCAL\t\tthis is a new message",
         MessageReader::REPLACE);
 
     // Check what is in the dictionary.
diff --git a/src/lib/log/tests/run_time_init_test.sh.in b/src/lib/log/tests/run_time_init_test.sh.in
index 1010566..e48a781 100755
--- a/src/lib/log/tests/run_time_init_test.sh.in
+++ b/src/lib/log/tests/run_time_init_test.sh.in
@@ -29,48 +29,49 @@ passfail() {
 # Create the local message file for testing
 
 cat > $localmes << .
-NOTHERE     this message is not in the global dictionary
-MSGRDERR    replacement read error, parameters: '%1' and '%2'
-UNRECDIR    replacement unrecognised directive message, parameter is '%1'
+\$PREFIX MSG_
+% NOTHERE     this message is not in the global dictionary
+% READERR     replacement read error, parameters: '%1' and '%2'
+% RDLOCMES    replacement read local message file, parameter is '%1'
 .
 
 echo -n "1. runInitTest default parameters: "
 cat > $tempfile << .
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
-WARN  [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
-INFO  [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, reading local message file dummy/file
+WARN  [alpha.dlm] MSG_READERR, error reading from message file a.txt: dummy reason
+INFO  [alpha.dlm] MSG_OPENIN, unable to open message file example.msg for input: dummy reason
 .
 ./logger_support_test | cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 echo -n "2. Severity filter: "
 cat > $tempfile << .
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, reading local message file dummy/file
 .
 ./logger_support_test -s error | cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 echo -n "3. Debug level: "
 cat > $tempfile << .
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
-WARN  [alpha.dlm] MSGRDERR, error reading from message file a.txt: dummy test
-INFO  [alpha.dlm] OPNMSGIN, unable to open message file example.msg for input: dummy test
-DEBUG [alpha.example] UNRECDIR, unrecognised directive '[abc]'
-DEBUG [alpha.example] UNRECDIR, unrecognised directive '[24]'
-DEBUG [alpha.example] UNRECDIR, unrecognised directive '[25]'
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, reading local message file dummy/file
+WARN  [alpha.dlm] MSG_READERR, error reading from message file a.txt: dummy reason
+INFO  [alpha.dlm] MSG_OPENIN, unable to open message file example.msg for input: dummy reason
+DEBUG [alpha.example] MSG_RDLOCMES, reading local message file dummy/0
+DEBUG [alpha.example] MSG_RDLOCMES, reading local message file dummy/24
+DEBUG [alpha.example] MSG_RDLOCMES, reading local message file dummy/25
 .
 ./logger_support_test -s debug -d 25 | cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 echo -n "4. Local message replacement: "
 cat > $tempfile << .
-WARN  [alpha.log] IDNOTFND, could not replace message for 'NOTHERE': no such message identification
-FATAL [alpha.example] MSGWRTERR, error writing to test1: 42
-ERROR [alpha.example] UNRECDIR, replacement unrecognised directive message, parameter is 'false'
-WARN  [alpha.dlm] MSGRDERR, replacement read error, parameters: 'a.txt' and 'dummy test'
+WARN  [alpha.log] MSG_IDNOTFND, could not replace message text for 'MSG_NOTHERE': no such message
+FATAL [alpha.example] MSG_WRITERR, error writing to test1: 42
+ERROR [alpha.example] MSG_RDLOCMES, replacement read local message file, parameter is 'dummy/file'
+WARN  [alpha.dlm] MSG_READERR, replacement read error, parameters: 'a.txt' and 'dummy reason'
 .
 ./logger_support_test -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
 passfail $?
diff --git a/src/lib/nsas/Makefile.am b/src/lib/nsas/Makefile.am
index 5f05f1b..3ecbca7 100644
--- a/src/lib/nsas/Makefile.am
+++ b/src/lib/nsas/Makefile.am
@@ -4,23 +4,39 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/nsas -I$(top_builddir)/src/lib/nsas
 AM_CPPFLAGS += $(SQLITE_CFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
-# Some versions of GCC warn about some versions of Boost regarding
-# missing initializer for members in its posix_time.
+# Some versions of GCC warn about some versions of Boost regarding missing
+# initializer for members in its posix_time.
 # https://svn.boost.org/trac/boost/ticket/3477
 # But older GCC compilers don't have the flag.
 AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
 
-if USE_CLANGPP
 # clang++ complains about unused function parameters in some boost header
 # files.
+if USE_CLANGPP
 AM_CXXFLAGS += -Wno-unused-parameter
 endif
 
+# Define rule to build logging source files from message file
+nsasdef.h nsasdef.cc: nsasdef.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/nsas/nsasdef.mes
+
+# What is being built.
 lib_LTLIBRARIES = libnsas.la
+
+# Tell Automake that the nsasdef.{cc,h} source files are created in the build
+# process, so it must create these before doing anything else.  Although they
+# are a dependency of the library (so will be created from the message file
+# anyway), there is no guarantee as to exactly _when_ in the build they will be
+# created.  As the .h file is included in other sources file (so must be
+# present when they are compiled), the safest option is to create it first.
+BUILT_SOURCES = nsasdef.h nsasdef.cc
+
+# Library sources. The generated files will not be in the distribution.
 libnsas_la_SOURCES  = address_entry.h address_entry.cc
 libnsas_la_SOURCES += asiolink.h
 libnsas_la_SOURCES += hash.cc hash.h
@@ -32,9 +48,16 @@ libnsas_la_SOURCES += nameserver_address.h nameserver_address.cc
 libnsas_la_SOURCES += nameserver_entry.cc nameserver_entry.h
 libnsas_la_SOURCES += nsas_entry_compare.h
 libnsas_la_SOURCES += nsas_entry.h nsas_types.h
+libnsas_la_SOURCES += nsas_log.cc nsas_log.h
 libnsas_la_SOURCES += zone_entry.cc zone_entry.h
 libnsas_la_SOURCES += fetchable.h
 libnsas_la_SOURCES += address_request_callback.h
 libnsas_la_SOURCES += glue_hints.h glue_hints.cc
 
-CLEANFILES = *.gcno *.gcda
+nodist_libnsas_la_SOURCES  = nsasdef.h nsasdef.cc
+
+# The message file should be in the distribution.
+EXTRA_DIST = nsasdef.mes
+
+# Make sure that the generated files are got rid of in a clean operation
+CLEANFILES = *.gcno *.gcda nsasdef.h nsasdef.cc
diff --git a/src/lib/nsas/nameserver_address_store.cc b/src/lib/nsas/nameserver_address_store.cc
index 3cef38d..ac55409 100644
--- a/src/lib/nsas/nameserver_address_store.cc
+++ b/src/lib/nsas/nameserver_address_store.cc
@@ -22,6 +22,7 @@
 #include <dns/rdataclass.h>
 #include <util/locks.h>
 #include <util/lru_list.h>
+#include <log/logger.h>
 
 #include "hash_table.h"
 #include "hash_deleter.h"
@@ -31,6 +32,8 @@
 #include "zone_entry.h"
 #include "glue_hints.h"
 #include "address_request_callback.h"
+#include "nsasdef.h"
+#include "nsas_log.h"
 
 using namespace isc::dns;
 using namespace std;
@@ -84,6 +87,8 @@ NameserverAddressStore::lookup(const string& zone, const RRClass& class_code,
     boost::shared_ptr<AddressRequestCallback> callback, AddressFamily family,
     const GlueHints& glue_hints)
 {
+    LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_LOOKUPZONE).arg(zone);
+
     pair<bool, boost::shared_ptr<ZoneEntry> > zone_obj(
         zone_hash_->getOrAdd(HashKey(zone, class_code),
                              boost::bind(newZone, resolver_, &zone, &class_code,
@@ -103,6 +108,8 @@ NameserverAddressStore::cancel(const string& zone,
     const boost::shared_ptr<AddressRequestCallback>& callback,
     AddressFamily family)
 {
+    LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_LOOKUPCANCEL).arg(zone);
+
     boost::shared_ptr<ZoneEntry> entry(zone_hash_->get(HashKey(zone,
                                                                class_code)));
     if (entry) {
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
index 4035f79..65b2ec2 100644
--- a/src/lib/nsas/nameserver_entry.cc
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -40,6 +40,7 @@
 #include "address_entry.h"
 #include "nameserver_address.h"
 #include "nameserver_entry.h"
+#include "nsas_log.h"
 
 using namespace isc::asiolink;
 using namespace isc::nsas;
@@ -178,6 +179,9 @@ NameserverEntry::updateAddressRTTAtIndex(uint32_t rtt, size_t index,
         new_rtt = 1;
     }
     addresses_[family][index].setRTT(new_rtt);
+    LOG_DEBUG(nsas_logger, NSAS_DBG_RTT, NSAS_SETRTT)
+              .arg(addresses_[family][index].getAddress().toText())
+              .arg(old_rtt).arg(new_rtt);
 }
 
 void
@@ -203,7 +207,7 @@ NameserverEntry::setAddressUnreachable(const IOAddress& address) {
  * \short A callback into the resolver.
  *
  * Whenever we ask the resolver something, this is created and the answer is
- * fed back trough this. It holds a shared pointer to the entry so it is not
+ * fed back through this. It holds a shared pointer to the entry so it is not
  * destroyed too soon.
  */
 class NameserverEntry::ResolverCallback :
@@ -230,6 +234,7 @@ class NameserverEntry::ResolverCallback :
             if (!response_message ||
                 response_message->getRcode() != isc::dns::Rcode::NOERROR() ||
                 response_message->getRRCount(isc::dns::Message::SECTION_ANSWER) == 0) {
+                LOG_ERROR(nsas_logger, NSAS_INVRESPSTR).arg(entry_->getName());
                 failureInternal(lock);
                 return;
             }
@@ -243,7 +248,12 @@ class NameserverEntry::ResolverCallback :
             if (response->getType() != type_ ||
                 response->getClass() != RRClass(entry_->getClass()))
             {
-                // TODO Log we got answer of different type
+                // Invalid response type or class
+                LOG_ERROR(nsas_logger, NSAS_INVRESPTC)
+                          .arg(entry_->getName()).arg(type_)
+                          .arg(entry_->getClass()).arg(response->getType())
+                          .arg(response->getClass());
+
                 failureInternal(lock);
                 return;
             }
@@ -264,8 +274,10 @@ class NameserverEntry::ResolverCallback :
                     }
                 }
                 // If we found it, use it. If not, create a new one.
-                entries.push_back(found ? *found : AddressEntry(IOAddress(
-                    i->getCurrent().toText()), 1));
+                entries.push_back(found ? *found : AddressEntry(
+                                                   IOAddress(address), 1));
+                LOG_DEBUG(nsas_logger, NSAS_DBG_RESULTS, NSAS_NSLKUPSUCC)
+                          .arg(address).arg(entry_->getName());
             }
 
             // We no longer need the previous set of addresses, we have
@@ -310,6 +322,8 @@ class NameserverEntry::ResolverCallback :
          * So mark the current address family as unreachable.
          */
         virtual void failure() {
+            LOG_DEBUG(nsas_logger, NSAS_DBG_RESULTS, NSAS_NSLKUPFAIL)
+                      .arg(type_).arg(entry_->getName());
             Lock lock(entry_->mutex_);
             failureInternal(lock);
         }
@@ -422,6 +436,8 @@ NameserverEntry::askIP(isc::resolve::ResolverInterface* resolver,
         // Ask for both types of addresses
         // We are unlocked here, as the callback from that might want to lock
         lock.unlock();
+
+        LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_NSADDR).arg(getName());
         askIP(resolver, RRType::A(), V4_ONLY);
         askIP(resolver, RRType::AAAA(), V6_ONLY);
         // Make sure we end the routine when we are not locked
diff --git a/src/lib/nsas/nsas_log.cc b/src/lib/nsas/nsas_log.cc
new file mode 100644
index 0000000..931b131
--- /dev/null
+++ b/src/lib/nsas/nsas_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "nsas/nsas_log.h"
+
+namespace isc {
+namespace nsas {
+
+isc::log::Logger nsas_logger("nsas");
+
+} // namespace nsas
+} // namespace isc
+
diff --git a/src/lib/nsas/nsas_log.h b/src/lib/nsas/nsas_log.h
new file mode 100644
index 0000000..9631988
--- /dev/null
+++ b/src/lib/nsas/nsas_log.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __NSAS_LOG__H
+#define __NSAS_LOG__H
+
+#include <log/macros.h>
+#include "nsasdef.h"
+
+namespace isc {
+namespace nsas {
+
+/// \brief NSAS Logging
+///
+/// Defines the levels used to output debug messages in the NSAS.  Note that
+/// higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations - asking the NSAS for an address,
+// and cancelling a lookup.  It also records when the NSAS calls back to the
+// resolver to resolve something.
+const int NSAS_DBG_TRACE = 10;
+
+// The next level extends the normal operations and records the results of the
+// lookups.
+const int NSAS_DBG_RESULTS = 20;
+
+// Additional information on the usage of the names - the RTT values obtained
+// when queries were done.
+const int NSAS_DBG_RTT = 30;
+
+
+/// \brief NSAS Logger
+///
+/// Define the logger used to log messages.  We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger nsas_logger;    // isc::nsas::logger is the NSAS logger
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __NSAS_LOG__H
diff --git a/src/lib/nsas/nsasdef.mes b/src/lib/nsas/nsasdef.mes
new file mode 100644
index 0000000..0f32d09
--- /dev/null
+++ b/src/lib/nsas/nsasdef.mes
@@ -0,0 +1,61 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$PREFIX NSAS_
+$NAMESPACE isc::nsas
+
+% INVRESPSTR      queried for %1 but got invalid response
+This message indicates an internal error in the nameserver address store
+component (NSAS) of the resolver.  The NSAS made a query for a RR for the
+specified nameserver but received an invalid response.  Either the success
+function was called without a DNS message or the message was invalid on some
+way. (In the latter case, the error should have been picked up elsewhere in
+the processing logic, hence the raising of the error here.)
+
+% INVRESPTC       queried for %1 RR of type/class %2/%3, received response %4/%5
+This message indicates an internal error in the nameserver address store
+component (NSAS) of the resolver.  The NSAS made a query for the given RR
+type and class, but instead received an answer with the given type and class.
+
+% LOOKUPCANCEL    lookup for zone %1 has been cancelled
+A debug message, this is output when a NSAS (nameserver address store -
+part of the resolver) lookup for a zone has been cancelled.
+
+% LOOKUPZONE      searching NSAS for nameservers for zone %1
+A debug message, this is output when a call is made to the nameserver address
+store (part of the resolver) to obtain the nameservers for the specified zone.
+
+% NSADDR          asking resolver to obtain A and AAAA records for %1
+A debug message, the NSAS (nameserver address store - part of the resolver) is
+making a callback into the resolver to retrieve the address records for the
+specified nameserver.
+
+% NSLKUPFAIL      failed to lookup any %1 for %2
+A debug message, the NSAS (nameserver address store - part of the resolver)
+has been unable to retrieve the specified resource record for the specified
+nameserver.  This is not necessarily a problem - the nameserver may be
+unreachable, in which case the NSAS will try other nameservers in the zone.
+
+% NSLKUPSUCC      found address %1 for %2
+A debug message, the NSAS (nameserver address store - part of the resolver)
+has retrieved the given address for the specified nameserver through an
+external query.
+
+% SETRTT          reporting RTT for %1 as %2; new value is now %3
+A NSAS (nameserver address store - part of the resolver) debug message
+reporting the round-trip time (RTT) for a query made to the specified
+nameserver.  The RTT has been updated using the value given and the new RTT is
+displayed.  (The RTT is subject to a calculation that damps out sudden
+changes.  As a result, the new RTT is not necessarily equal to the RTT
+reported.)
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index 56f36d1..e9235ba 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -53,6 +53,7 @@ endif
 
 run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/lib/nsas/tests/run_unittests.cc b/src/lib/nsas/tests/run_unittests.cc
index d1277ad..bc672d0 100644
--- a/src/lib/nsas/tests/run_unittests.cc
+++ b/src/lib/nsas/tests/run_unittests.cc
@@ -13,14 +13,23 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+#include <stdlib.h>
+
+#include <string>
+#include <boost/lexical_cast.hpp>
 
 #include <gtest/gtest.h>
 
 #include <dns/tests/unittest_util.h>
+#include <log/logger_support.h>
+
+using namespace std;
 
 int
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
 
+    isc::log::initLogger();
+
     return (RUN_ALL_TESTS());
 }
diff --git a/src/lib/python/bind10_config.py.in b/src/lib/python/bind10_config.py.in
index 3f2947d..4a38903 100644
--- a/src/lib/python/bind10_config.py.in
+++ b/src/lib/python/bind10_config.py.in
@@ -17,7 +17,37 @@
 # variables to python scripts and libraries.
 import os
 
-BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
-                                       "@PACKAGE_NAME@",
-                                       "msgq_socket").replace("${prefix}",
-                                                              "@prefix@")
+def reload():
+    # In a function, for testing purposes
+    global BIND10_MSGQ_SOCKET_FILE
+    global DATA_PATH
+    global PLUGIN_PATHS
+    global PREFIX
+    BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
+                                           "@PACKAGE_NAME@",
+                                           "msgq_socket").replace("${prefix}",
+                                                                  "@prefix@")
+    PREFIX = "@prefix@"
+
+    # If B10_FROM_SOURCE is set in the environment, we use data files
+    # from a directory relative to the value of that variable, or, if defined,
+    # relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR.  Otherwise
+    # we use the ones installed on the system.
+    # B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
+    # tests where we want to use variuos types of configuration within the test
+    # environment.  (We may want to make it even more generic so that the path is
+    # passed from the boss process)
+    if "B10_FROM_SOURCE" in os.environ:
+        if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+            DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+        else:
+            DATA_PATH = os.environ["B10_FROM_SOURCE"]
+        PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
+    else:
+        DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
+        PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
+    # For testing the plugins so they can find their own spec files
+    if "B10_TEST_PLUGIN_DIR" in os.environ:
+        PLUGIN_PATHS = os.environ["B10_TEST_PLUGIN_DIR"].split(':')
+
+reload()
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index 43dc7af..178a983 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -142,13 +142,17 @@ class NotifyOut:
         if zone_id not in self._notify_infos:
             return
 
+        # Has no slave servers, skip it.
+        if (len(self._notify_infos[zone_id].notify_slaves) <= 0):
+            return
+
         with self._lock:
             if (self.notify_num >= _MAX_NOTIFY_NUM) or (zone_id in self._notifying_zones):
                 if zone_id not in self._waiting_zones:
                     self._waiting_zones.append(zone_id)
             else:
                 self._notify_infos[zone_id].prepare_notify_out()
-                self.notify_num += 1 
+                self.notify_num += 1
                 self._notifying_zones.append(zone_id)
 
     def _dispatcher(self, started_event):
@@ -300,7 +304,7 @@ class NotifyOut:
         try:
             r_fds, w, e = select.select(valid_socks, [], [], block_timeout)
         except select.error as err:
-            if err.args[0] != EINTR:
+            if err.args[0] != errno.EINTR:
                 return {}, {}
 
         if self._read_sock in r_fds: # user has called shutdown()
diff --git a/src/lib/python/isc/notify/tests/notify_out_test.py b/src/lib/python/isc/notify/tests/notify_out_test.py
index c4c149c..44725d0 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -99,36 +99,51 @@ class TestNotifyOut(unittest.TestCase):
         self._notify._notify_infos[('example.org.', 'IN')] = MockZoneNotifyInfo('example.org.', 'IN')
         self._notify._notify_infos[('example.org.', 'CH')] = MockZoneNotifyInfo('example.org.', 'CH')
 
-        info = self._notify._notify_infos[('example.net.', 'IN')]
-        info.notify_slaves.append(('127.0.0.1', 53))
-        info.notify_slaves.append(('1.1.1.1', 5353))
+        net_info = self._notify._notify_infos[('example.net.', 'IN')]
+        net_info.notify_slaves.append(('127.0.0.1', 53))
+        net_info.notify_slaves.append(('1.1.1.1', 5353))
+        com_info = self._notify._notify_infos[('example.com.', 'IN')]
+        com_info.notify_slaves.append(('1.1.1.1', 5353))
+        com_ch_info = self._notify._notify_infos[('example.com.', 'CH')]
+        com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
 
     def tearDown(self):
         self._db_file.close()
         os.unlink(self._db_file.name)
 
     def test_send_notify(self):
+        notify_out._MAX_NOTIFY_NUM = 2
+
         self._notify.send_notify('example.net')
         self.assertEqual(self._notify.notify_num, 1)
-        self.assertEqual(self._notify._notifying_zones[0], ('example.net.','IN'))
+        self.assertEqual(self._notify._notifying_zones[0], ('example.net.', 'IN'))
 
         self._notify.send_notify('example.com')
         self.assertEqual(self._notify.notify_num, 2)
-        self.assertEqual(self._notify._notifying_zones[1], ('example.com.','IN'))
+        self.assertEqual(self._notify._notifying_zones[1], ('example.com.', 'IN'))
 
-        notify_out._MAX_NOTIFY_NUM = 3
+        # notify_num is equal to MAX_NOTIFY_NUM, append it to waiting_zones list.
         self._notify.send_notify('example.com', 'CH')
-        self.assertEqual(self._notify.notify_num, 3)
-        self.assertEqual(self._notify._notifying_zones[2], ('example.com.','CH'))
-
-        self._notify.send_notify('example.org.')
-        self.assertEqual(self._notify._waiting_zones[0], ('example.org.', 'IN'))
-        self._notify.send_notify('example.org.')
+        self.assertEqual(self._notify.notify_num, 2)
         self.assertEqual(1, len(self._notify._waiting_zones))
 
+        # zone_id is already in notifying_zones list, append it to waiting_zones list.
+        self._notify.send_notify('example.net')
+        self.assertEqual(2, len(self._notify._waiting_zones))
+        self.assertEqual(self._notify._waiting_zones[1], ('example.net.', 'IN'))
+
+        # zone_id is already in waiting_zones list, skip it.
+        self._notify.send_notify('example.net')
+        self.assertEqual(2, len(self._notify._waiting_zones))
+
+        # has no slave masters, skip it.
         self._notify.send_notify('example.org.', 'CH')
+        self.assertEqual(self._notify.notify_num, 2)
+        self.assertEqual(2, len(self._notify._waiting_zones))
+
+        self._notify.send_notify('example.org.')
+        self.assertEqual(self._notify.notify_num, 2)
         self.assertEqual(2, len(self._notify._waiting_zones))
-        self.assertEqual(self._notify._waiting_zones[1], ('example.org.', 'CH'))
 
     def test_wait_for_notify_reply(self):
         self._notify.send_notify('example.net.')
@@ -171,6 +186,7 @@ class TestNotifyOut(unittest.TestCase):
         self._notify.send_notify('example.net.')
         self._notify.send_notify('example.com.')
         notify_out._MAX_NOTIFY_NUM = 2
+        # zone example.org. has no slave servers.
         self._notify.send_notify('example.org.')
         self._notify.send_notify('example.com.', 'CH')
 
@@ -179,17 +195,19 @@ class TestNotifyOut(unittest.TestCase):
         self.assertEqual(0, info.notify_try_num)
         self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
         self.assertEqual(2, self._notify.notify_num)
+        self.assertEqual(1, len(self._notify._waiting_zones))
 
         self._notify._notify_next_target(info)
         self.assertEqual(0, info.notify_try_num)
         self.assertIsNone(info.get_current_notify_target())
         self.assertEqual(2, self._notify.notify_num)
-        self.assertEqual(1, len(self._notify._waiting_zones))
+        self.assertEqual(0, len(self._notify._waiting_zones))
 
         example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
         self._notify._notify_next_target(example_com_info)
-        self.assertEqual(2, self._notify.notify_num)
-        self.assertEqual(2, len(self._notify._notifying_zones))
+        self.assertEqual(1, self._notify.notify_num)
+        self.assertEqual(1, len(self._notify._notifying_zones))
+        self.assertEqual(0, len(self._notify._waiting_zones))
 
     def test_handle_notify_reply(self):
         self.assertEqual(notify_out._BAD_REPLY_PACKET, self._notify._handle_notify_reply(None, b'badmsg'))
diff --git a/src/lib/python/isc/util/Makefile.am b/src/lib/python/isc/util/Makefile.am
index 7ab8048..f6cbb78 100644
--- a/src/lib/python/isc/util/Makefile.am
+++ b/src/lib/python/isc/util/Makefile.am
@@ -1,5 +1,5 @@
 SUBDIRS = . tests
 
-python_PYTHON = __init__.py process.py socketserver_mixin.py
+python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
 
 pythondir = $(pyexecdir)/isc/util
diff --git a/src/lib/python/isc/util/file.py b/src/lib/python/isc/util/file.py
new file mode 100644
index 0000000..faef9a8
--- /dev/null
+++ b/src/lib/python/isc/util/file.py
@@ -0,0 +1,29 @@
+# Copyright (C) 2011  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.
+
+"""Various functions for working with files and directories."""
+
+from os.path import exists, join
+
+def path_search(filename, paths):
+    """
+    Searches list of paths to find filename in one of them. The found one will
+    be returned or IOError will be returned if it isn't found.
+    """
+    for p in paths:
+        f = join(p, filename)
+        if exists(f):
+            return f
+    raise IOError("'" + filename + "' not found in " + str(paths))
diff --git a/src/lib/python/isc/util/tests/Makefile.am b/src/lib/python/isc/util/tests/Makefile.am
index f32fda0..0ce96de 100644
--- a/src/lib/python/isc/util/tests/Makefile.am
+++ b/src/lib/python/isc/util/tests/Makefile.am
@@ -1,5 +1,5 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = process_test.py socketserver_mixin_test.py
+PYTESTS = process_test.py socketserver_mixin_test.py file_test.py
 EXTRA_DIST = $(PYTESTS)
 
 # test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/util/tests/file_test.py b/src/lib/python/isc/util/tests/file_test.py
new file mode 100644
index 0000000..fb765d7
--- /dev/null
+++ b/src/lib/python/isc/util/tests/file_test.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2011  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.
+
+import isc.util.file
+import unittest
+
+class FileTest(unittest.TestCase):
+    def test_search_path_find(self):
+        """Test it returns the first occurence of the file"""
+        self.assertEqual('./Makefile',
+                         isc.util.file.path_search('Makefile',
+                                                  ['/no/such/directory/', '.',
+                                                  '../tests/']))
+
+    def test_search_path_notfound(self):
+        """Test it throws an exception when the file can't be found"""
+        self.assertRaises(IOError, isc.util.file.path_search, 'no file', ['/no/such/directory'])
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
index 34411ee..b753cc9 100644
--- a/src/lib/resolve/recursive_query.cc
+++ b/src/lib/resolve/recursive_query.cc
@@ -191,13 +191,12 @@ private:
     // Info for (re)sending the query (the question and destination)
     Question question_;
 
+    // This is the query message got from client
+    ConstMessagePtr query_message_;
+
     // This is where we build and store our final answer
     MessagePtr answer_message_;
 
-    // currently we use upstream as the current list of NS records
-    // we should differentiate between forwarding and resolving
-    boost::shared_ptr<AddressVector> upstream_;
-
     // Test server - only used for testing.  This takes precedence over all
     // other servers if the port is non-zero.
     std::pair<std::string, uint16_t> test_server_;
@@ -344,13 +343,8 @@ private:
         }
     }
     
-    // 'general' send; if we are in forwarder mode, send a query to
-    // a random nameserver in our forwarders list. If we are in
-    // recursive mode, ask the NSAS to give us an address.
+    // 'general' send, ask the NSAS to give us an address.
     void send(IOFetch::Protocol protocol = IOFetch::UDP) {
-        // If are in forwarder mode, send it to a random
-        // forwarder. If not, ask the NSAS for an address
-        const int uc = upstream_->size();
         protocol_ = protocol;   // Store protocol being used for this
         if (test_server_.second != 0) {
             dlog("Sending upstream query (" + question_.toText() +
@@ -362,18 +356,6 @@ private:
                 test_server_.second, buffer_, this,
                 query_timeout_);
             io_.get_io_service().post(query);
-        } else if (uc > 0) {
-            // TODO: use boost, or rand()-utility function we provide
-            int serverIndex = rand() % uc;
-            dlog("Sending upstream query (" + question_.toText() +
-                ") to " + upstream_->at(serverIndex).first);
-            ++outstanding_events_;
-            gettimeofday(&current_ns_qsent_time, NULL);
-            IOFetch query(protocol, io_, question_,
-                upstream_->at(serverIndex).first,
-                upstream_->at(serverIndex).second, buffer_, this,
-                query_timeout_);
-            io_.get_io_service().post(query);
         } else {
             // Ask the NSAS for an address for the current zone,
             // the callback will call the actual sendTo()
@@ -548,7 +530,6 @@ public:
     RunningQuery(IOService& io,
         const Question& question,
         MessagePtr answer_message,
-        boost::shared_ptr<AddressVector> upstream,
         std::pair<std::string, uint16_t>& test_server,
         OutputBufferPtr buffer,
         isc::resolve::ResolverInterface::CallbackPtr cb,
@@ -560,8 +541,8 @@ public:
         :
         io_(io),
         question_(question),
+        query_message_(),
         answer_message_(answer_message),
-        upstream_(upstream),
         test_server_(test_server),
         buffer_(buffer),
         resolvercallback_(cb),
@@ -709,8 +690,7 @@ public:
                 incoming.fromWire(ibuf);
 
                 buffer_->clear();
-                if (recursive_mode() &&
-                    incoming.getRcode() == Rcode::NOERROR()) {
+                if (incoming.getRcode() == Rcode::NOERROR()) {
                     done_ = handleRecursiveAnswer(incoming);
                 } else {
                     isc::resolve::copyResponseMessage(incoming, answer_message_);
@@ -744,13 +724,11 @@ public:
         } else if (!done_ && retries_--) {
             // Query timed out, but we have some retries, so send again
             dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", resending query");
-            if (recursive_mode()) {
-                current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
-            }
+            current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
             send();
         } else {
             // We are either already done, or out of retries
-            if (recursive_mode() && result == IOFetch::TIME_OUT) {
+            if (result == IOFetch::TIME_OUT) {
                 dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", giving up");
                 current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
             }
@@ -767,12 +745,179 @@ public:
     void makeSERVFAIL() {
         isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL());
     }
-    
-    // Returns true if we are in 'recursive' mode
-    // Returns false if we are in 'forwarding' mode
-    // (i.e. if we have anything in upstream_)
-    bool recursive_mode() const {
-        return upstream_->empty();
+};
+
+class ForwardQuery : public IOFetch::Callback {
+private:
+    // The io service to handle async calls
+    IOService& io_;
+
+    // This is the query message got from client
+    ConstMessagePtr query_message_;
+
+    // This is where we build and store our final answer
+    MessagePtr answer_message_;
+
+    // List of nameservers to forward to
+    boost::shared_ptr<AddressVector> upstream_;
+
+    // Buffer to store the result.
+    OutputBufferPtr buffer_;
+
+    // This will be notified when we succeed or fail
+    isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
+
+    /*
+     * TODO Do something more clever with timeouts. In the long term, some
+     *     computation of average RTT, increase with each retry, etc.
+     */
+    // Timeout information
+    int query_timeout_;
+
+    // TODO: replace by our wrapper
+    asio::deadline_timer client_timer;
+    asio::deadline_timer lookup_timer;
+
+    // Make FowardQuery deletes itself safely. for more information see
+    // the comments of outstanding_events in RunningQuery.
+    size_t outstanding_events_;
+
+    // If we have a client timeout, we call back with a failure message,
+    // but we do not stop yet. We use this variable to make sure we
+    // don't call back a second time later
+    bool callback_called_;
+
+    // send the query to the server.
+    void send(IOFetch::Protocol protocol = IOFetch::UDP) {
+        const int uc = upstream_->size();
+        buffer_->clear();
+        int serverIndex = rand() % uc;
+        ConstQuestionPtr question = *(query_message_->beginQuestion());
+        dlog("Sending upstream query (" + question->toText() +
+             ") to " + upstream_->at(serverIndex).first);
+        ++outstanding_events_;
+        // Forward the query, create the IOFetch with
+        // query message, so that query flags can be forwarded
+        // together.
+        IOFetch query(protocol, io_, query_message_,
+            upstream_->at(serverIndex).first,
+            upstream_->at(serverIndex).second,
+            buffer_, this, query_timeout_);
+
+        io_.get_io_service().post(query);
+    }
+
+public:
+    ForwardQuery(IOService& io,
+        ConstMessagePtr query_message,
+        MessagePtr answer_message,
+        boost::shared_ptr<AddressVector> upstream,
+        OutputBufferPtr buffer,
+        isc::resolve::ResolverInterface::CallbackPtr cb,
+        int query_timeout, int client_timeout, int lookup_timeout) :
+        io_(io),
+        query_message_(query_message),
+        answer_message_(answer_message),
+        upstream_(upstream),
+        buffer_(buffer),
+        resolvercallback_(cb),
+        query_timeout_(query_timeout),
+        client_timer(io.get_io_service()),
+        lookup_timer(io.get_io_service()),
+        outstanding_events_(0),
+        callback_called_(false)
+    {
+        // Setup the timer to stop trying (lookup_timeout)
+        if (lookup_timeout >= 0) {
+            lookup_timer.expires_from_now(
+                boost::posix_time::milliseconds(lookup_timeout));
+            ++outstanding_events_;
+            lookup_timer.async_wait(boost::bind(&ForwardQuery::lookupTimeout, this));
+        }
+
+        // Setup the timer to send an answer (client_timeout)
+        if (client_timeout >= 0) {
+            client_timer.expires_from_now(
+                boost::posix_time::milliseconds(client_timeout));
+            ++outstanding_events_;
+            client_timer.async_wait(boost::bind(&ForwardQuery::clientTimeout, this));
+        }
+
+        send();
+    }
+
+    virtual void lookupTimeout() {
+        if (!callback_called_) {
+            makeSERVFAIL();
+            callCallback(false);
+        }
+        assert(outstanding_events_ > 0);
+        --outstanding_events_;
+        stop();
+    }
+
+    virtual void clientTimeout() {
+        if (!callback_called_) {
+            makeSERVFAIL();
+            callCallback(false);
+        }
+        assert(outstanding_events_ > 0);
+        --outstanding_events_;
+        stop();
+    }
+
+    // If the callback has not been called yet, call it now
+    // If success is true, we call 'success' with our answer_message
+    // If it is false, we call failure()
+    void callCallback(bool success) {
+        if (!callback_called_) {
+            callback_called_ = true;
+            if (success) {
+                resolvercallback_->success(answer_message_);
+            } else {
+                resolvercallback_->failure();
+            }
+        }
+    }
+
+    virtual void stop() {
+        // if we cancel our timers, we will still get an event for
+        // that, so we cannot delete ourselves just yet (those events
+        // would be bound to a deleted object)
+        // cancel them one by one, both cancels should get us back
+        // here again.
+        // same goes if we have an outstanding query (can't delete
+        // until that one comes back to us)
+        lookup_timer.cancel();
+        client_timer.cancel();
+        if (outstanding_events_ > 0) {
+            return;
+        } else {
+            delete this;
+        }
+    }
+
+    // This function is used as callback from DNSQuery.
+    virtual void operator()(IOFetch::Result result) {
+        // XXX is this the place for TCP retry?
+        assert(outstanding_events_ > 0);
+        --outstanding_events_;
+        if (result != IOFetch::TIME_OUT) {
+            // we got an answer
+            Message incoming(Message::PARSE);
+            InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+            incoming.fromWire(ibuf);
+            isc::resolve::copyResponseMessage(incoming, answer_message_);
+            callCallback(true);
+        }
+
+        stop();
+    }
+
+    // Clear the answer parts of answer_message, and set the rcode
+    // to servfail
+    void makeSERVFAIL() {
+        isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL());
     }
 };
 
@@ -815,7 +960,7 @@ RecursiveQuery::resolve(const QuestionPtr& question,
         } else {
             dlog("Message not found in cache, starting recursive query");
             // It will delete itself when it is done
-            new RunningQuery(io, *question, answer_message, upstream_,
+            new RunningQuery(io, *question, answer_message,
                              test_server_, buffer, callback,
                              query_timeout_, client_timeout_,
                              lookup_timeout_, retries_, nsas_,
@@ -869,7 +1014,7 @@ RecursiveQuery::resolve(const Question& question,
         } else {
             dlog("Message not found in cache, starting recursive query");
             // It will delete itself when it is done
-            new RunningQuery(io, question, answer_message, upstream_,
+            new RunningQuery(io, question, answer_message, 
                              test_server_, buffer, crs, query_timeout_,
                              client_timeout_, lookup_timeout_, retries_,
                              nsas_, cache_, rtt_recorder_);
@@ -877,5 +1022,36 @@ RecursiveQuery::resolve(const Question& question,
     }
 }
 
+void
+RecursiveQuery::forward(ConstMessagePtr query_message,
+    MessagePtr answer_message,
+    OutputBufferPtr buffer,
+    DNSServer* server,
+    isc::resolve::ResolverInterface::CallbackPtr callback)
+{
+    // XXX: eventually we will need to be able to determine whether
+    // the message should be sent via TCP or UDP, or sent initially via
+    // UDP and then fall back to TCP on failure, but for the moment
+    // we're only going to handle UDP.
+    IOService& io = dns_service_.getIOService();
+
+    if (!callback) {
+        callback.reset(new isc::resolve::ResolverCallbackServer(server));
+    }
+
+    // TODO: general 'prepareinitialanswer'
+    answer_message->setOpcode(isc::dns::Opcode::QUERY());
+    ConstQuestionPtr question = *query_message->beginQuestion();
+    answer_message->addQuestion(*question);
+
+    // implement the simplest forwarder, which will pass
+    // everything throught without interpretation, except
+    // QID, port number. The response will not be cached.
+    // It will delete itself when it is done
+    new ForwardQuery(io, query_message, answer_message,
+                      upstream_, buffer, callback, query_timeout_,
+                      client_timeout_, lookup_timeout_);
+}
+
 } // namespace asiodns
 } // namespace isc
diff --git a/src/lib/resolve/recursive_query.h b/src/lib/resolve/recursive_query.h
index c082426..b9fb80d 100644
--- a/src/lib/resolve/recursive_query.h
+++ b/src/lib/resolve/recursive_query.h
@@ -141,6 +141,20 @@ public:
                  isc::util::OutputBufferPtr buffer,
                  DNSServer* server);
 
+    /// \brief Initiates forwarding for the given query.
+    ///
+    ///  Others parameters are same with the parameters of
+    ///  function resolve().
+    ///
+    /// \param query_message the full query got from client.
+    /// \param callback callback object
+    void forward(isc::dns::ConstMessagePtr query_message,
+                 isc::dns::MessagePtr answer_message,
+                 isc::util::OutputBufferPtr buffer,
+                 DNSServer* server,
+                 isc::resolve::ResolverInterface::CallbackPtr callback =
+                     isc::resolve::ResolverInterface::CallbackPtr());
+
     /// \brief Set Test Server
     ///
     /// This method is *only* for unit testing the class.  If set, it enables
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index 04a7803..4e939fa 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -37,6 +37,7 @@
 
 #include <nsas/nameserver_address_store.h>
 #include <cache/resolver_cache.h>
+#include <resolve/resolve.h>
 
 // IMPORTANT: We shouldn't directly use ASIO definitions in this test.
 // In particular, we must not include asio.hpp in this file.
@@ -579,9 +580,12 @@ TEST_F(RecursiveQueryTest, forwarderSend) {
                       singleAddress(TEST_IPV4_ADDR, port));
 
     Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
+    Message query_message(Message::RENDER);
+    isc::resolve::initResponseMessage(q, query_message);
+
     OutputBufferPtr buffer(new OutputBuffer(0));
     MessagePtr answer(new Message(Message::RENDER));
-    rq.resolve(q, answer, buffer, &server);
+    rq.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
 
     char data[4096];
     size_t size = sizeof(data);
@@ -646,8 +650,41 @@ bool tryRead(int sock_, int recv_options, size_t max, int* num) {
     return true;
 }
 
+// Mock resolver callback for testing forward query.
+class MockResolverCallback : public isc::resolve::ResolverInterface::Callback {
+public:
+    enum ResultValue {
+        DEFAULT = 0,
+        SUCCESS = 1,
+        FAILURE = 2
+    };
+
+    MockResolverCallback(DNSServer* server):
+        result(DEFAULT),
+        server_(server->clone())
+    {}
+
+    ~MockResolverCallback() {
+        delete server_;
+    }
+
+    void success(const isc::dns::MessagePtr response) {
+        result = SUCCESS;
+        server_->resume(true);
+    }
+
+    void failure() {
+        result = FAILURE;
+        server_->resume(false);
+    }
 
-// Test it tries the correct amount of times before giving up
+    uint32_t result;
+private:
+    DNSServer* server_;
+};
+
+// Test query timeout, set query timeout is lower than client timeout
+// and lookup timeout.
 TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
     // Prepare the service (we do not use the common setup, we do not answer
     setDNSService();
@@ -669,26 +706,20 @@ TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
     MessagePtr answer(new Message(Message::RENDER));
-    query.resolve(question, answer, buffer, &server);
+    Message query_message(Message::RENDER);
+    isc::resolve::initResponseMessage(question, query_message);
 
+    boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+    query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
     // Run the test
     io_service_->run();
-
-    // Read up to 3 packets.  Use some ad hoc timeout to prevent an infinite
-    // block (see also recvUDP()).
-    int recv_options = setSocketTimeout(sock_, 10, 0);
-    int num = 0;
-    bool read_success = tryRead(sock_, recv_options, 3, &num);
-
-    // The query should 'succeed' with an error response
-    EXPECT_TRUE(done);
-    EXPECT_EQ(3, num);
-    EXPECT_TRUE(read_success);
+    EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
 }
 
 // If we set client timeout to lower than querytimeout, we should
-// get a failure answer, but still see retries
-// (no actual answer is given here yet)
+// get a failure answer
+// (no actual answer is given here yet. TODO the returned error message
+// should be tested)
 TEST_F(RecursiveQueryTest, forwardClientTimeout) {
     // Prepare the service (we do not use the common setup, we do not answer
     setDNSService();
@@ -703,36 +734,25 @@ TEST_F(RecursiveQueryTest, forwardClientTimeout) {
 
     // Do the answer
     const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    // Set it up to retry twice before client timeout fires
-    // Since the lookup timer has not fired, it should retry
-    // four times
     RecursiveQuery query(*dns_service_,
                          *nsas_, cache_,
                          singleAddress(TEST_IPV4_ADDR, port),
                          singleAddress(TEST_IPV4_ADDR, port),
-                         200, 480, 4000, 4);
-    Question question(Name("example.net"), RRClass::IN(), RRType::A());
+                         1000, 10, 4000, 4);
+    Question q(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    query.resolve(question, answer, buffer, &server);
+    Message query_message(Message::RENDER);
+    isc::resolve::initResponseMessage(q, query_message);
 
+    boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+    query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
     // Run the test
     io_service_->run();
-
-    // we know it'll fail, so make it a shorter timeout
-    int recv_options = setSocketTimeout(sock_, 1, 0);
-
-    // Try to read 4 times
-    int num = 0;
-    bool read_success = tryRead(sock_, recv_options, 4, &num);
-
-    // The query should fail
-    EXPECT_TRUE(done1);
-    EXPECT_EQ(3, num);
-    EXPECT_FALSE(read_success);
+    EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
 }
 
-// If we set lookup timeout to lower than querytimeout*retries, we should
-// fail before the full amount of retries
+// If we set lookup timeout to lower than querytimeout, the lookup
+// will fail.
 TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
     // Prepare the service (we do not use the common setup, we do not answer
     setDNSService();
@@ -748,30 +768,22 @@ TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
 
     // Do the answer
     const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    // Set up the test so that it will retry 5 times, but the lookup
-    // timeout will fire after only 3 normal timeouts
     RecursiveQuery query(*dns_service_,
                          *nsas_, cache_,
                          singleAddress(TEST_IPV4_ADDR, port),
                          singleAddress(TEST_IPV4_ADDR, port),
-                         200, 4000, 480, 5);
+                         1000, 4000, 10, 5);
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    query.resolve(question, answer, buffer, &server);
 
+    Message query_message(Message::RENDER);
+    isc::resolve::initResponseMessage(question, query_message);
+
+    boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+    query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
     // Run the test
     io_service_->run();
-
-    int recv_options = setSocketTimeout(sock_, 1, 0);
-
-    // Try to read 5 times, should stop after 3 reads
-    int num = 0;
-    bool read_success = tryRead(sock_, recv_options, 5, &num);
-
-    // The query should fail and respond with an error
-    EXPECT_TRUE(done);
-    EXPECT_EQ(3, num);
-    EXPECT_FALSE(read_success);
+    EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
 }
 
 // Set everything very low and see if this doesn't cause weird
@@ -791,8 +803,6 @@ TEST_F(RecursiveQueryTest, lowtimeouts) {
 
     // Do the answer
     const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    // Set up the test so that it will retry 5 times, but the lookup
-    // timeout will fire after only 3 normal timeouts
     RecursiveQuery query(*dns_service_,
                          *nsas_, cache_,
                          singleAddress(TEST_IPV4_ADDR, port),
@@ -800,21 +810,15 @@ TEST_F(RecursiveQueryTest, lowtimeouts) {
                          1, 1, 1, 1);
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    query.resolve(question, answer, buffer, &server);
 
+    Message query_message(Message::RENDER);
+    isc::resolve::initResponseMessage(question, query_message);
+
+    boost::shared_ptr<MockResolverCallback> callback(new MockResolverCallback(&server));
+    query.forward(ConstMessagePtr(&query_message), answer, buffer, &server, callback);
     // Run the test
     io_service_->run();
-
-    int recv_options = setSocketTimeout(sock_, 1, 0);
-
-    // Try to read 5 times, should stop after 3 reads
-    int num = 0;
-    bool read_success = tryRead(sock_, recv_options, 5, &num);
-
-    // The query should fail and respond with an error
-    EXPECT_TRUE(done);
-    EXPECT_EQ(1, num);
-    EXPECT_FALSE(read_success);
+    EXPECT_EQ(callback->result, MockResolverCallback::FAILURE);
 }
 
 // as mentioned above, we need a more better framework for this,
diff --git a/src/lib/server_common/Makefile.am b/src/lib/server_common/Makefile.am
index dfb3014..a3063ba 100644
--- a/src/lib/server_common/Makefile.am
+++ b/src/lib/server_common/Makefile.am
@@ -18,9 +18,12 @@ endif
 
 lib_LTLIBRARIES = libserver_common.la
 libserver_common_la_SOURCES = portconfig.h portconfig.cc
+libserver_common_la_SOURCES += keyring.h keyring.cc
 libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/config/libcfgclient.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 
 CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/server_common/keyring.cc b/src/lib/server_common/keyring.cc
new file mode 100644
index 0000000..f68db70
--- /dev/null
+++ b/src/lib/server_common/keyring.cc
@@ -0,0 +1,61 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <server_common/keyring.h>
+
+using namespace isc::dns;
+using namespace isc::data;
+
+namespace isc {
+namespace server_common {
+
+typedef boost::shared_ptr<TSIGKeyRing> KeyringPtr;
+
+KeyringPtr keyring;
+
+namespace {
+
+void
+updateKeyring(const std::string&, ConstElementPtr data) {
+    ConstElementPtr list(data->get("keys"));
+    KeyringPtr load(new TSIGKeyRing);
+    for (size_t i(0); i < list->size(); ++ i) {
+        load->add(TSIGKey(list->get(i)->stringValue()));
+    }
+    keyring.swap(load);
+}
+
+}
+
+void
+initKeyring(config::ModuleCCSession& session) {
+    if (keyring) {
+        // We are already initialized
+        return;
+    }
+    session.addRemoteConfig("tsig_keys", updateKeyring, false);
+}
+
+void
+deinitKeyring(config::ModuleCCSession& session) {
+    if (!keyring) {
+        // Not initialized, ignore it
+        return;
+    }
+    keyring.reset();
+    session.removeRemoteConfig("tsig_keys");
+}
+
+}
+}
diff --git a/src/lib/server_common/keyring.h b/src/lib/server_common/keyring.h
new file mode 100644
index 0000000..8832095
--- /dev/null
+++ b/src/lib/server_common/keyring.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ISC_SERVER_COMMON_KEYRING_H
+#define ISC_SERVER_COMMON_KEYRING_H
+
+#include <boost/shared_ptr.hpp>
+#include <dns/tsigkey.h>
+#include <config/ccsession.h>
+
+/**
+ * \file keyring.h
+ * \brief TSIG keyring loaded from configuration.
+ *
+ * This file contains routines for loading a TSIG key ring from
+ * the tsig_keys configuration section and keeping them up to date
+ * on updates.
+ *
+ * You simply initialize/load the keyring with isc::server_common::initKeyring
+ * and then just use the key ring in in isc::server_common::keyring. It is
+ * automatically reloaded, when the configuration updates, so you no longer
+ * needs to care about it.
+ *
+ * If you want to keep a key (or session) for longer time or your application
+ * is multithreaded, you might want to have a copy of the shared pointer.
+ * Otherwise an update might replace the keyring and delete the keys in the
+ * old one.
+ */
+
+namespace isc {
+
+namespace server_common {
+
+/**
+ * \brief The key ring itself
+ *
+ * This is where the key ring is stored. You can directly use it to your needs,
+ * but you need to call initKeyring first, otherwise you'll find a NULL pointer
+ * here only.
+ */
+extern boost::shared_ptr<dns::TSIGKeyRing> keyring;
+
+/**
+ * \brief Load the key ring for the first time
+ *
+ * This loads the key ring from configuration to keyring. It also registers for
+ * config updates, so from now on, it'll be kept up to date.
+ *
+ * You can unload the key ring with deinitKeyring.
+ *
+ * If it is already loaded, this function does nothing. So, if more than one
+ * part of an application needs to use the key ring, they all can just call
+ * this independently to ensure the keyring is loaded.
+ *
+ * \param session The configuration session used to talk to the config manager.
+ */
+void
+initKeyring(config::ModuleCCSession& session);
+
+/**
+ * \brief Unload the key ring
+ *
+ * This can be used to unload the key ring. It will reset the keyring to NULL
+ * and stop receiving updates of the configuration.
+ *
+ * The need for this function should be quite rare, as it isn't required to be
+ * called before application shutdown. And not calling it has only small
+ * performance penalty -- the keyring will be kept in memory and updated when
+ * the user changes configuration.
+ *
+ * This does nothing if the key ring is not loaded currently.
+ *
+ * \param session The configuration session used to talk to the config manager.
+ *
+ * \todo What do we do when the data that come are invalid? Should we ignore it,
+ *     as walidity should have been checked already in the config manager, or
+ *     throw? What about when we get an update and it's invalid?
+ */
+void
+deinitKeyring(config::ModuleCCSession& session);
+
+}
+}
+
+#endif
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index a04a884..e711002 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/src/lib/server_common/tests/Makefile.am
@@ -27,6 +27,8 @@ if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES += portconfig_unittest.cc
+run_unittests_SOURCES += keyring_test.cc
+run_unittests_SOURCES += data_path.h
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -38,6 +40,8 @@ run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libasiodns.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
 endif
 
 noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/server_common/tests/data_path.h.in b/src/lib/server_common/tests/data_path.h.in
new file mode 100644
index 0000000..8ac0380
--- /dev/null
+++ b/src/lib/server_common/tests/data_path.h.in
@@ -0,0 +1,16 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")   
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#define TEST_DATA_PATH "@abs_srcdir@/testdata"
+#define PLUGIN_DATA_PATH "@top_srcdir@/src/bin/cfgmgr/plugins"
diff --git a/src/lib/server_common/tests/keyring_test.cc b/src/lib/server_common/tests/keyring_test.cc
new file mode 100644
index 0000000..6d2f226
--- /dev/null
+++ b/src/lib/server_common/tests/keyring_test.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <server_common/keyring.h>
+#include <server_common/tests/data_path.h>
+
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+#include <dns/name.h>
+
+#include <gtest/gtest.h>
+#include <memory>
+#include <string>
+
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::server_common;
+using namespace isc::dns;
+
+namespace {
+
+class KeyringTest : public ::testing::Test {
+public:
+    KeyringTest() :
+        session(ElementPtr(new ListElement), ElementPtr(new ListElement),
+                ElementPtr(new ListElement)),
+        specfile(std::string(TEST_DATA_PATH) + "/spec.spec")
+    {
+        session.getMessages()->add(createAnswer());
+        mccs.reset(new ModuleCCSession(specfile, session, NULL, NULL));
+    }
+    isc::cc::FakeSession session;
+    std::auto_ptr<ModuleCCSession> mccs;
+    std::string specfile;
+    void doInit() {
+        // Prepare the module specification for it and the config
+        session.getMessages()->
+            add(createAnswer(0,
+                             moduleSpecFromFile(std::string(PLUGIN_DATA_PATH) +
+                                                "/tsig_keys.spec").
+                             getFullSpec()));
+        session.getMessages()->add(createAnswer(0, Element::fromJSON(
+            "{\"keys\": [\"key:MTIzNAo=:hmac-sha1\"]}")));
+        // Now load it
+        EXPECT_NO_THROW(initKeyring(*mccs));
+        EXPECT_NE(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+            "No keyring even after init";
+    }
+};
+
+// Test usual use - init, using the keyring, update, deinit
+TEST_F(KeyringTest, keyring) {
+    // First, initialize it
+    {
+        SCOPED_TRACE("Init");
+        doInit();
+
+        // Make sure it contains the correct key
+        TSIGKeyRing::FindResult result(keyring->find(Name("key"),
+                                                     TSIGKey::HMACSHA1_NAME()));
+        EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
+    }
+
+    {
+        SCOPED_TRACE("Update");
+        session.addMessage(createCommand("config_update", Element::fromJSON(
+            "{\"keys\": [\"another:MTIzNAo=:hmac-sha256\"]}")),
+                           "tsig_keys", "*");
+        mccs->checkCommand();
+
+        // Make sure it no longer contains the original key
+        TSIGKeyRing::FindResult result(keyring->find(Name("key"),
+                                                     TSIGKey::HMACSHA1_NAME()));
+        EXPECT_EQ(TSIGKeyRing::NOTFOUND, result.code);
+        // but it does contain the new one
+        TSIGKeyRing::FindResult result2 = keyring->find(Name("another"),
+            TSIGKey::HMACSHA256_NAME());
+        EXPECT_EQ(TSIGKeyRing::SUCCESS, result2.code);
+    }
+
+    {
+        SCOPED_TRACE("Deinit");
+        deinitKeyring(*mccs);
+        EXPECT_EQ(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+            "The keyring didn't disappear";
+    }
+}
+
+// Init twice
+TEST_F(KeyringTest, initTwice) {
+    // It is NULL before
+    EXPECT_EQ(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+        "Someone forgot to deinit it before";
+    {
+        SCOPED_TRACE("First init");
+        doInit();
+    }
+    boost::shared_ptr<TSIGKeyRing> backup(keyring);
+    {
+        SCOPED_TRACE("Second init");
+        EXPECT_NO_THROW(initKeyring(*mccs)) <<
+            "It not only does something when it is already initialized, "
+            "it even throws at it";
+    }
+    EXPECT_EQ(backup, keyring) << "The second init replaced the data";
+    deinitKeyring(*mccs);
+}
+
+// deinit when not initialized
+TEST_F(KeyringTest, extraDeinit) {
+    // It is NULL before
+    EXPECT_EQ(boost::shared_ptr<TSIGKeyRing>(), keyring) <<
+        "Someone forgot to deinit it before";
+    // Check that it doesn't get confused when we do not have it initialized
+    EXPECT_NO_THROW(deinitKeyring(*mccs));
+    // It is still NULL
+    EXPECT_EQ(keyring, boost::shared_ptr<TSIGKeyRing>()) <<
+        "Where did it get something after deinit?";
+}
+
+}
diff --git a/src/lib/server_common/tests/testdata/spec.spec b/src/lib/server_common/tests/testdata/spec.spec
new file mode 100644
index 0000000..3e0a822
--- /dev/null
+++ b/src/lib/server_common/tests/testdata/spec.spec
@@ -0,0 +1,6 @@
+{
+    "module_spec": {
+        "module_name": "test"
+    }
+}
+
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index 77a3e4a..6deec27 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -24,5 +24,7 @@ libutil_la_SOURCES += encode/binary_from_base16.h
 libutil_la_SOURCES += random/qid_gen.h random/qid_gen.cc
 libutil_la_SOURCES += random/random_number_generator.h
 
+EXTRA_DIST = python/pycppwrapper_util.h
+
 libutil_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/python/mkpywrapper.py.in b/src/lib/util/python/mkpywrapper.py.in
new file mode 100755
index 0000000..4bf7752
--- /dev/null
+++ b/src/lib/util/python/mkpywrapper.py.in
@@ -0,0 +1,100 @@
+#!@PYTHON@
+
+# Copyright (C) 2011  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.
+
+"""This utility program generates a C++ header and implementation files
+that can be used as a template of C++ python binding for a C++ class.
+
+Usage: ./mkpywrapper.py ClassName
+(the script should be run on this directory)
+
+It will generate two files: classname_python.h and classname_python.cc,
+many of whose definitions are in the namespace isc::MODULE_NAME::python.
+By default MODULE_NAME will be 'dns' (because this tool is originally
+intended to be used for the C++ python binding of the DNS library), but
+can be changed via the -m command line option.
+
+The generated files contain code fragments that are commonly used in
+C++ python binding implementations.  It will define a class named
+s_ClassName which is a derived class of PyModule and can meet the
+requirement of the CPPPyObjectContainer template class (see
+pycppwrapper_util.h).  It also defines (and declares in the header file)
+"classname_type", which is of PyTypeObject and is intended to be used
+to define details of the python bindings for the ClassName class.
+
+In many cases the header file can be used as a startpoint of the
+binding development without modification.  But you may want to make
+ClassName::cppobj a constant variable (and you should if you can).
+Many definitions of classname_python.cc should also be able to be used
+just as defined, but some will need to be changed or removed.  In
+particular, you should at least adjust ClassName_init().  You'll
+probably also need to add more definitions to that file to provide
+complete features of the C++ class.
+"""
+
+import datetime, string, sys
+from optparse import OptionParser
+
+# Remember the current year to produce the copyright boilerplate
+YEAR = datetime.date.today().timetuple()[0]
+
+def dump_file(out_file, temp_file, class_name, module):
+    for line in temp_file.readlines():
+        line = line.replace("@YEAR@", str(YEAR))
+        line = line.replace("@CPPCLASS at _H", class_name.upper() + "_H")
+        line = line.replace("@CPPCLASS@", class_name)
+        line = line.replace("@cppclass@", class_name.lower())
+        line = line.replace("@MODULE@", module)
+        out_file.write(line)
+
+def dump_wrappers(class_name, output, module):
+    try:
+        if output == "-":
+            header_file = sys.stdout
+        else:
+            header_file = open(output + "_python.h", "w")
+        header_template_file = open("wrapper_template.h", "r")
+        if output == "-":
+            impl_file = sys.stdout
+        else:
+            impl_file = open(output + "_python.cc", "w")
+        impl_template_file = open("wrapper_template.cc", "r")
+    except:
+        sys.stderr.write('Failed to open C++ file(s)\n')
+        sys.exit(1)
+    dump_file(header_file, header_template_file, class_name, module)
+    dump_file(impl_file, impl_template_file, class_name, module)
+
+usage = '''usage: %prog [options] class_name'''
+
+if __name__ == "__main__":
+    parser = OptionParser(usage=usage)
+    parser.add_option('-o', '--output', action='store', dest='output',
+                      default=None, metavar='FILE',
+                      help='prefix of output file names [default: derived from the class name]')
+    parser.add_option('-m', '--module', action='store', dest='module',
+                      default='dns',
+                      help='C++ module name of the wrapper (for namespaces) [default: dns]')
+    (options, args) = parser.parse_args()
+
+    if len(args) == 0:
+        parser.error('input file is missing')
+
+    class_name = args[0]
+    if not options.output:
+        options.output = class_name.lower()
+
+    dump_wrappers(class_name, options.output, options.module)
diff --git a/src/lib/util/python/pycppwrapper_util.h b/src/lib/util/python/pycppwrapper_util.h
new file mode 100644
index 0000000..fd55c19
--- /dev/null
+++ b/src/lib/util/python/pycppwrapper_util.h
@@ -0,0 +1,308 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYCPPWRAPPER_UTIL_H
+#define __PYCPPWRAPPER_UTIL_H 1
+
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+/**
+ * @file pycppwrapper_util.h
+ * @short Shared definitions for python/C(++) API
+ *
+ * This utility defines a set of convenient wrappers for the python C API
+ * to use it safely from our C++ bindings.  The python C API has many pitfalls
+ * such as not-so-consistent reference count policies.  Also, many existing
+ * examples are careless about error handling.  It's easy to find on the net
+ * example (even of "production use") python extensions like this:
+ *
+ * \code
+ *     new_exception = PyErr_NewException("mymodule.Exception", NULL, NULL);
+ *     // new_exception can be NULL, in which case the call to
+ *     // PyModule_AddObject will cause a surprising disruption.
+ *     PyModule_AddObject(mymodule, "Exception", new_exception); \endcode
+ *
+ * When using the python C API with C++, we should also be careful about
+ * exception safety.  The underlying C++ code (including standard C++ libraries
+ * and memory allocation) can throw exceptions, in which case we need to
+ * make sure any intermediate python objects are cleaned up (we also need to
+ * catch the C++ exceptions inside the binding and convert them to python
+ * errors, but that's a different subject).  This is not a trivial task
+ * because the python objects are represented as bare C pointers (so there's
+ * no destructor) and we need to address the exception safety along with python
+ * reference counters (so we cannot naively apply standard smart pointers).
+ *
+ * This utility tries to help address these issues.
+ *
+ * Also, it's intentional that this is a header-only utility.  This way the
+ * C++ loadable module won't depend on another C++ library (which is not
+ * necessarily wrong, but would increase management cost such as link-time
+ * troubles only for a small utility feature).
+ */
+
+namespace isc {
+namespace util {
+namespace python {
+
+/// This is thrown inside this utility when it finds a NULL pointer is passed
+/// when it should not be NULL.
+class PyCPPWrapperException : public isc::Exception {
+public:
+    PyCPPWrapperException(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// This helper class is similar to the standard autoptr and manages PyObject
+/// using some kind of RAII techniques.  It is, however, customized for the
+/// python C API.
+///
+/// A PyObjectContainer object is constructed with a pointer to PyObject,
+/// which is often just created dynamically.  The caller will eventually
+/// attach the object to a different python object (often a module or class)
+/// via specific methods or directly return it to the python interpreter.
+///
+/// There are two cases in destructing the object: with or without decreasing
+/// a reference to the PyObject.  If the object is intended to be an argument
+/// to another python C library that increases the reference to the object for
+/// itself, we should normally release our own reference; otherwise the
+/// reference will leak and the object won't be garbage collected.  Also, when
+/// an unexpected error happens in the form of C++ exception, we should
+/// release the reference to prevent resource leak.
+///
+/// In some other cases, we should simply give our reference to the caller.
+/// That is the case when the created object itself is a return value of
+/// an extended python method written in the C++ binding.  Likewise, some
+/// python C library functions "steal" the reference.  In these cases we
+/// should not decrease the reference; otherwise it would cause duplicate free.
+///
+/// By default, the destructor of this class releases the reference to the
+/// PyObject.  If this behavior is desirable, you can extract the original
+/// bare pointer to the PyObject by the \c get() method.  If you don't want
+/// the reference to be decreased, the original bare pointer should be
+/// extracted using the \c release() method.
+///
+/// There are two convenience methods for commonly used operations:
+/// \c installAsClassVariable() to add the PyObject as a class variable
+/// and \c installToModule to add the PyObject to a specified python module.
+/// These methods (at least to some extent) take care of the reference to
+/// the object (either release or keep) depending on the usage context so
+/// that the user don't have to worry about it.
+///
+/// On construction, this class expects the pointer can be NULL.
+/// If it happens it immediately throws a \c PyCPPWrapperException exception.
+/// This behavior is to convert failures in the python C API (such as
+/// PyObject_New() returning NULL) to C++ exception so that we can unify
+/// error handling in the style of C++ exceptions.
+///
+/// Examples 1: To create a tuple of two python objects, do this:
+///
+/// \code
+///     try {
+///         PyObjectContainer container0(Py_BuildValue("I", 0));
+///         PyObjectContainer container1(Py_BuildValue("s", cppobj.toText().c_str()));
+///         return (Py_BuildValue("OO", container0.get(), container1.get()));
+///     } catch { ... set python exception, etc ... } \endcode
+///
+/// Commonly deployed buggy implementation to achieve this would be like this:
+/// \code
+///    return (Py_BuildValue("OO", Py_BuildValue("I", 0),
+///                          Py_BuildValue("s", cppobj.toText().c_str())));
+/// \endcode
+/// One clear bug of this code is that references to the element objects of
+/// the tuple will leak.
+/// (Assuming \c cppobj.toText() can throw) this code is also not exception
+/// safe; if \c cppobj.toText() throws the reference to the first object
+/// will leak, even if the code tried to do the necessary cleanup in the
+/// successful case.
+/// Further, this code naively passes the result of the first two calls to
+/// \c Py_BuildValue() to the third one even if they can be NULL.
+/// In this specific case, it happens to be okay because \c Py_BuildValue()
+/// accepts NULL and treats it as an indication of error.  But not all
+/// python C library works that way (remember, the API is so inconsistent)
+/// and we need to refer to the API manual every time we have to worry about
+/// passing a NULL object to a library function.  We'd certainly like to
+/// avoid such development overhead.  The code using \c PyObjectContainer
+/// addresses all these problems.
+///
+/// Examples 2: Install a (constant) variable to a class.
+///
+/// \code
+///    try {
+///        // installClassVariable is a wrapper of
+///        // PyObjectContainer::installAsClassVariable.  See below.
+///        installClassVariable(myclass_type, "SOME_CONSTANT",
+///                             Py_BuildValue("I", 0));
+///    } catch { ... }
+/// \endcode
+///
+/// Examples 3: Install a custom exception to a module.
+///
+/// \code
+///    PyObject* new_exception; // publicly visible
+///    ...
+///    try {
+///        new_exception = PyErr_NewException("mymodule.NewException",
+///                                           NULL, NULL);
+///        PyObjectContainer(new_exception).installToModule(mymodule,
+///                                                         "NewException");
+///    } catch { ... }
+/// \endcode
+///
+/// Note that \c installToModule() keeps the reference to \c new_exception
+/// by default.  This is a common practice when we introduce a custom
+/// exception in a python biding written in C/C++.  See the code comment
+/// of the method for more details.
+struct PyObjectContainer {
+    PyObjectContainer(PyObject* obj) : obj_(obj) {
+        if (obj_ == NULL) {
+            isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, "
+                      "probably due to short memory");
+        }
+    }
+    virtual ~PyObjectContainer() {
+        if (obj_ != NULL) {
+            Py_DECREF(obj_);
+        }
+    }
+    PyObject* get() {
+        return (obj_);
+    }
+    PyObject* release() {
+        PyObject* ret = obj_;
+        obj_ = NULL;
+        return (ret);
+    }
+
+    // Install the enclosed PyObject to the specified python class 'pyclass'
+    // as a variable named 'name'.
+    void installAsClassVariable(PyTypeObject& pyclass, const char* name) {
+        if (PyDict_SetItemString(pyclass.tp_dict, name, obj_) < 0) {
+            isc_throw(PyCPPWrapperException, "Failed to set a class variable, "
+                      "probably due to short memory");
+        }
+        // Ownership successfully transferred to the class object.  We'll let
+        // it be released in the destructor.
+    }
+
+    // Install the enclosed PyObject to the specified module 'mod' as an
+    // object named 'name'.
+    // By default, this method explicitly keeps the reference to the object
+    // even after the module "steals" it.  To cancel this behavior and give
+    // the reference to the module completely, the third parameter 'keep_ref'
+    // should be set to false.
+    void installToModule(PyObject* mod, const char* name,
+                         bool keep_ref = true)
+    {
+        if (PyModule_AddObject(mod, name, obj_) < 0) {
+            isc_throw(PyCPPWrapperException, "Failed to add an object to "
+                      "module, probably due to short memory");
+        }
+        // PyModule_AddObject has "stolen" the reference, so unless we
+        // have to retain it ourselves we don't (shouldn't) decrease it.
+        // However, we actually often need to keep our own reference because
+        // objects added to a module are often referenced via non local
+        // C/C++ variables in various places of the C/C++ code.  In order
+        // for the code to run safely even if some buggy/evil python program
+        // performs 'del mod.obj', we need the extra reference.  See, e.g.:
+        // http://docs.python.org/py3k/c-api/init.html#Py_Initialize
+        // http://mail.python.org/pipermail/python-dev/2005-June/054238.html
+        if (keep_ref) {
+            Py_INCREF(obj_);
+        }
+        obj_ = NULL;
+    }
+
+protected:
+    PyObject* obj_;
+};
+
+/// This templated class is a derived class of \c PyObjectContainer and
+/// manages C++-class based python objects.
+///
+/// The template parameter \c PYSTRUCT must be a derived class (structure) of
+/// \c PyObject that has a member variable named \c cppobj, which must be a
+/// a pointer to \c CPPCLASS (the second template parameter).
+///
+/// For example, to define a custom python class based on a C++ class, MyClass,
+/// we'd define a class (struct) named \c s_MyClass like this:
+/// \code
+///    class s_MyClass : public PyObject {
+///    public:
+///       s_MyClass() : cppobj(NULL) {}
+///       MyClass* cppobj;
+///    };
+/// \endcode
+///
+/// And, to build and return a python version of MyClass object, write the
+/// following C++ code:
+/// \code
+///    typedef CPPPyObjectContainer<s_MyClass, MyClass> MyContainer;
+///    try {
+///        // below, myclass_type is of \c PyTypeObject that defines
+///        // a python class (type) for MyClass
+///        MyContainer container(PyObject_New(s_MyClass, myclass_type));
+///        container.set(new MyClass());
+///        return (container.release()); // give the reference to the caller
+///    } catch { ... }
+/// \endcode
+///
+/// This code prevents bugs like NULL pointer dereference when \c PyObject_New
+/// fails or resource leaks when new'ing \c MyClass results in an exception.
+/// Note that we use \c release() (derived from the base class) instead of
+/// \c get(); in this case we should simply pass the reference generated in
+/// \c PyObject_New() to the caller.
+template <typename PYSTRUCT, typename CPPCLASS>
+struct CPPPyObjectContainer : public PyObjectContainer {
+    CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
+
+    // This method associates a C++ object with the corresponding python
+    // object enclosed in this class.
+    void set(CPPCLASS* value) {
+        if (value == NULL) {
+            isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
+                      "probably due to short memory");
+        }
+        static_cast<PYSTRUCT*>(obj_)->cppobj = value;
+    }
+
+    // This is a convenience short cut to associate a C++ object with the
+    // python object and install it to the specified python class \c pyclass
+    // as a variable named \c name.
+    void installAsClassVariable(PyTypeObject& pyclass, const char* name,
+                                CPPCLASS* value)
+    {
+        set(value);
+        PyObjectContainer::installAsClassVariable(pyclass, name);
+    }
+};
+
+/// A shortcut function to install a python class variable.
+///
+/// It installs a python object \c obj to a specified class \c pyclass
+/// as a variable named \c name.
+inline void
+installClassVariable(PyTypeObject& pyclass, const char* name, PyObject* obj) {
+    PyObjectContainer(obj).installAsClassVariable(pyclass, name);
+}
+
+} // namespace python
+} // namespace util
+} // namespace isc
+#endif // __PYCPPWRAPPER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/python/wrapper_template.cc b/src/lib/util/python/wrapper_template.cc
new file mode 100644
index 0000000..2fe7567
--- /dev/null
+++ b/src/lib/util/python/wrapper_template.cc
@@ -0,0 +1,293 @@
+// Copyright (C) @YEAR@  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 <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include "@cppclass at _python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::@MODULE@;
+using namespace isc::@MODULE@::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// @CPPCLASS@
+//
+
+// Trivial constructor.
+s_ at CPPCLASS@::s_ at CPPCLASS@() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_ at CPPCLASS@, @CPPCLASS@> @CPPCLASS at Container;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int @CPPCLASS at _init(s_ at CPPCLASS@* self, PyObject* args);
+void @CPPCLASS at _destroy(s_ at CPPCLASS@* self);
+
+// These are the functions we export
+// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
+//
+PyObject* @CPPCLASS at _toText(const s_ at CPPCLASS@* const self);
+PyObject* @CPPCLASS at _str(PyObject* self);
+PyObject* @CPPCLASS at _richcmp(const s_ at CPPCLASS@* const self,
+                            const s_ at CPPCLASS@* const other, int op);
+
+// This is quite specific pydnspp.  For other wrappers this should probably
+// be removed.
+PyObject* @CPPCLASS at _toWire(const s_ at CPPCLASS@* self, PyObject* args);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef @CPPCLASS at _methods[] = {
+    { "to_text", reinterpret_cast<PyCFunction>(@CPPCLASS at _toText), METH_NOARGS,
+      "Returns the text representation" },
+    // This is quite specific pydnspp.  For other wrappers this should probably
+    // be removed:
+    { "to_wire", reinterpret_cast<PyCFunction>(@CPPCLASS at _toWire), METH_VARARGS,
+      "Converts the @CPPCLASS@ object to wire format.\n"
+      "The argument can be either a MessageRenderer or an object that "
+      "implements the sequence interface. If the object is mutable "
+      "(for instance a bytearray()), the wire data is added in-place.\n"
+      "If it is not (for instance a bytes() object), a new object is "
+      "returned" },
+    { NULL, NULL, 0, NULL }
+};
+
+// This is a template of typical code logic of python class initialization
+// with C++ backend.  You'll need to adjust it according to details of the
+// actual C++ class.
+int
+ at CPPCLASS@_init(s_ at CPPCLASS@* self, PyObject* args) {
+    try {
+        if (PyArg_ParseTuple(args, "REPLACE ME")) {
+            // YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
+            self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct @CPPCLASS@ object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing @CPPCLASS@");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to @CPPCLASS@ constructor");
+
+    return (-1);
+}
+
+// This is a template of typical code logic of python object destructor.
+// In many cases you can use it without modification, but check that carefully.
+void
+ at CPPCLASS@_destroy(s_ at CPPCLASS@* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+// This should be able to be used without modification as long as the
+// underlying C++ class has toText().
+PyObject*
+ at CPPCLASS@_toText(const s_ at CPPCLASS@* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert @CPPCLASS@ object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting @CPPCLASS@ object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+ at CPPCLASS@_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject* 
+ at CPPCLASS@_richcmp(const s_ at CPPCLASS@* const self,
+                   const s_ at CPPCLASS@* const other,
+                   const int op)
+{
+    bool c = false;
+
+    // Check for null and if the types match. If different type,
+    // simply return False
+    if (other == NULL || (self->ob_type != other->ob_type)) {
+        Py_RETURN_FALSE;
+    }
+
+    // Only equals and not equals here, unorderable type
+    switch (op) {
+    case Py_LT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_LE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_EQ:
+        c = (*self->cppobj == *other->cppobj);
+        break;
+    case Py_NE:
+        c = (*self->cppobj != *other->cppobj);
+        break;
+    case Py_GT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_GE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    }
+    if (c) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace @MODULE@ {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_ at CPPCLASS@
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject @cppclass at _type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python. at CPPCLASS@",
+    sizeof(s_ at CPPCLASS@),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(@CPPCLASS at _destroy),       // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    @CPPCLASS at _str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The @CPPCLASS@ class objects is...(COMPLETE THIS)",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    reinterpret_cast<richcmpfunc>(@CPPCLASS at _richcmp), // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    @CPPCLASS at _methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    reinterpret_cast<initproc>(@CPPCLASS at _init),            // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_ at CPPCLASS@(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&@cppclass at _type) < 0) {
+        return (false);
+    }
+    void* p = &@cppclass at _type;
+    if (PyModule_AddObject(mod, "@CPPCLASS@", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&@cppclass at _type);
+
+    // The following template is the typical procedure for installing class
+    // variables.  If the class doesn't have a class variable, remove the
+    // entire try-catch clauses.
+    try {
+        // Constant class variables
+        installClassVariable(@cppclass at _type, "REPLACE_ME",
+                             Py_BuildValue("REPLACE ME"));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in @CPPCLASS@ initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in @CPPCLASS@ initialization");
+        return (false);
+    }
+
+    return (true);
+}
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc
diff --git a/src/lib/util/python/wrapper_template.h b/src/lib/util/python/wrapper_template.h
new file mode 100644
index 0000000..1f983e4
--- /dev/null
+++ b/src/lib/util/python/wrapper_template.h
@@ -0,0 +1,44 @@
+// Copyright (C) @YEAR@  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 __PYTHON_ at CPPCLASS@_H
+#define __PYTHON_ at CPPCLASS@_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace @MODULE@ {
+class @CPPCLASS@;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_ at CPPCLASS@ : public PyObject {
+public:
+    s_ at CPPCLASS@();
+    @CPPCLASS@* cppobj;
+};
+
+extern PyTypeObject @cppclass at _type;
+
+bool initModulePart_ at CPPCLASS@(PyObject* mod);
+
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc
+#endif // __PYTHON_ at CPPCLASS@_H
+
+// Local Variables:
+// mode: c++
+// End:




More information about the bind10-changes mailing list