BIND 10 trac2559, updated. ff5861d1c7d7a689d775b251086d12032fcabd54 [2559] Remove elements with empty values from database access string

BIND 10 source code commits bind10-changes at lists.isc.org
Thu Jan 10 19:41:53 UTC 2013


The branch, trac2559 has been updated
       via  ff5861d1c7d7a689d775b251086d12032fcabd54 (commit)
       via  aae75525fe351f4df368402e7e05aa0e52b234b1 (commit)
       via  5267d959d14763af2a7c938a136267655722cd77 (commit)
       via  6a47ad417ae15c4cc9bad53347ae06088a642ec3 (commit)
       via  1c54eac8eb70d3fc7c16b83a1269315e710021c1 (commit)
       via  bed3c88c25ea8f7e951317775e99ebce3340ca22 (commit)
       via  2d95931bbb2efb3ae30b6fe7219958e40b51c4f8 (commit)
       via  0c40d70c28cda1bc61871cd0e26b74656f45114d (commit)
       via  37a27e19be874725ea3d560065e5591a845daa89 (commit)
       via  d365482e0e132c96812b7b99c465582a027035f2 (commit)
       via  bd9b58b2875c100b68e3f246a09975bde0a13e16 (commit)
       via  b3a9b362643eddb59ef78ff392cf091d0c199163 (commit)
       via  f8ad67f7166f71b5f78721cc2d7df85c2a859bbc (commit)
       via  ccd941541d51c3a3eb3df162e094637ad8317f63 (commit)
       via  741fe7bc96c70df35d9a79016b0aa1488e9b3ac8 (commit)
       via  856d60a96bbed5735541659d7c12c51fc055a6cb (commit)
       via  792c129a0785c73dd28fd96a8f1439fe6534a3f1 (commit)
       via  b85908b8e8321ae1b1ab139c7182a5026bc7f73d (commit)
       via  3e191cf2889f9ef77561ebac18e1cc019d1bffe4 (commit)
       via  b789222291eeb63126dc1c249ed89d4f4517db63 (commit)
       via  6e4caf9dd4f4239776d1e05efbfe13987e5bf9a5 (commit)
       via  2548205bf705e3f2d9aeb75e35eb112206476d03 (commit)
       via  8399e2b6d96d18197dc27b9f4bee1f5051f6c7af (commit)
       via  3c162ca506c8fc08ab93374521807b2132e81c24 (commit)
       via  5eb2ff86d745543185551758bf31132596ad20d9 (commit)
       via  0de32e7fe21e18069d26e3d282ac8078c852f004 (commit)
       via  fb10ec4e31a94ed4c98fa83c5ef3064f8e53a39e (commit)
       via  83e71312ca3631e5a8f2364bb8434d69ae000ee1 (commit)
       via  b55b8b6686cc80eed41793c53d1779f4de3e9e3c (commit)
       via  7234eee9cf529a98841ca502dde655bd15f4299f (commit)
       via  806865cd2e830785f0d377d759e7475b9e1f640b (commit)
       via  e7f9ab53656fdfb8a1b27c7056f8dc2c86d8f966 (commit)
       via  35d02013f29d19fabf678089ba97db2e67ab3f25 (commit)
       via  004823862f69853d3c4acd6f8e0bc60925c25ed1 (commit)
       via  1fa69473d881a5c31cd802563f2c398e1f6d1ecb (commit)
       via  3baea8cf898f2e9dcd35c88e48999b8112732f9d (commit)
       via  08c2f82000bacb76d0602cc9c428637aed0baf21 (commit)
       via  be570bcaf88318286027da7da40a600cbe4223e9 (commit)
       via  e5a354694f4d0192132d00e25223d16eb24f25d7 (commit)
       via  a92495cd29fbe405bef744b98d01ff200caa05c6 (commit)
       via  f516fc484544b7e08475947d6945bc87636d4115 (commit)
       via  f1e9ef1f4906cfc43cf76308675d24a851739105 (commit)
       via  96e05ecaa1e2c19c28ff5d1f50efdcff7e3e7d5c (commit)
       via  c6f04a39f70ad71571779b734e7a3593e6c2f69f (commit)
       via  96f973347163030737690174068f51c193a2f4e6 (commit)
       via  c85c06e93473453547d4a378ebc3c12ef74ae68c (commit)
       via  202d9dd4ef7d116f844029a34c36fbdb48921ba0 (commit)
       via  18831888667fe9363c22d3d19db9b0f98e56ef70 (commit)
       via  b0a3df2d384689dad61a47084b5dfa5a00918885 (commit)
       via  de2886172153001a862453ee4ed73bcd07db2ae0 (commit)
       via  58c136da23483524f053d3caa20210941bfa13db (commit)
       via  cb50f4f6d306ebd40d7ff03b1494dc52b96b191d (commit)
       via  7cee8494a729694402e9ce19d257d643888b7a4c (commit)
       via  15a69fd77a14825ca0deb1cba52690bf0ea33195 (commit)
       via  50e76dc960ad76ec97004832d718cb337390fce8 (commit)
       via  3e9a69424117d8f9841d76cf71dab353b6aa5435 (commit)
       via  890e0b0b7df76e61c1638744647a793143537722 (commit)
       via  a6ce7e4332223277ca50cd68a65e7e404621717c (commit)
       via  d83fc2a12f36818bdd60c87e61d01be7a5dfa4e5 (commit)
       via  46efce68984c6ab8c5d02618e2b9ca21bf46de06 (commit)
       via  7bea86f90950a22f72d0c6271d7086ab59abdfa0 (commit)
       via  1f011110511b06ca175f19fecf0ce522140c6f95 (commit)
       via  9e85dc2c98c9fde0f1d73c1d6c0d0d6188b41439 (commit)
       via  36622df85e6b1b7ce4fc621fbfaa21d2b35003d8 (commit)
       via  bf592d0607a639ff454dcdc820a76ae0eaacbbf4 (commit)
       via  2ed60b95b8cbdff758e08062fc82c8d8f7b367ac (commit)
       via  059fcd024b1611a7c5ca52a6f7055f7c9d14f106 (commit)
       via  d078381e41cf375a3504655ec921999828c284e2 (commit)
       via  c6e109f7d68347703baa542d0a913e97394b8057 (commit)
       via  9fbb0ae95365974d80a87704f64d6e4907dcb52e (commit)
       via  4cb0974a3a642d06513ecc43743109bd92e45a08 (commit)
       via  573d2b1fbc3145d18ec71621f56e5c74a328ed3c (commit)
       via  d831b49beab09fe65b95d39858486824e9b9a4da (commit)
       via  2d107bd1f98f6a3aed2120213d713111246e6535 (commit)
       via  57e0fe723d9769cb893ae168f7d9374280670c4d (commit)
       via  28f38670d37fe76c2481df6b8f82284ce4949de2 (commit)
       via  9a2fd23cdd58afe5dbc932dead8f10b0ac22605c (commit)
       via  b8e5414ee0445b3b0141203470ac07f42d7bbc32 (commit)
       via  aa32f38e127d1b3b3d4e1349fbfdcac155dc5c5e (commit)
       via  40502d5e2c6cbe71143e842ac7908e8a55b62324 (commit)
       via  c6981a0eed4cb0c68f747fa7f758e53dbfdaf5f7 (commit)
       via  385f156f420aa17a1d6f07bcb17a87075f9d4492 (commit)
       via  0fe0d44af6e81f2e5230515237fcc84b42f37d85 (commit)
       via  60b979b7088f9e282b173c8d71792e2c4b20b57c (commit)
       via  0974318566abe08d0702ddd185156842c6642424 (commit)
       via  31147f0f82fd1e1b8ead164153599269822517ea (commit)
       via  8679b4053024172db8e3d55b7ed50a855efc77c0 (commit)
       via  5530bff261fd921726fce87c2d296da5271c639f (commit)
       via  dd444334eb873bd925639e3141c3a863fad1e47a (commit)
       via  5d3af5f896df69f42590921a44650745411673fa (commit)
       via  bbb8a8f0ed42862b7e5b54e7611cb0d0e364203b (commit)
       via  ee424bbc727b754b8030fb2adaa69c578f096de9 (commit)
       via  bdf19add5fca40dbc91c6ce4c0d87d8bb6080bde (commit)
       via  cfbb9eada0a6e0ff8a2faead73f870d3ca3400fc (commit)
       via  8bca74f57a81fa7e2dc2569692b253dbf19270b4 (commit)
       via  34929974f3655f19029d83234e4c5b89b85eaf99 (commit)
       via  c466b2fb0a4888db16ff0888dc0f8c9875d6f43c (commit)
       via  e2f99db35b161d951e60aee4728118cf41c3b3d4 (commit)
       via  f47a693087d2583eee59f97e9b43c40535443688 (commit)
       via  9faa2925c3934e3526b5b24694ec0986a6303d42 (commit)
       via  469a7104b7231ef26e9f08e85e9f4acf035398c4 (commit)
       via  4d7307519b21a0364350cbac4560f6693430cd6b (commit)
       via  86f542bb1fecca6e8d4b93a3462649b9dc17805f (commit)
       via  a12ebb8fdcf246c1a096d62c624185c96f88b186 (commit)
       via  0fd48fc7f5406e8cbd8b459a7fc5231053519d3b (commit)
       via  df8823fdb350670a1e5333b335b59a56e8b09b4c (commit)
       via  3e100854e0a85d215d614729f6d429340be1ead2 (commit)
       via  a146bf1575760b55554d3d77520ea0900b9a4d51 (commit)
       via  3965fb5302e46eb4e7763a328add0a83687a8309 (commit)
       via  810c95b11f458b18c196a39cba8f3d47ac1d6cb2 (commit)
       via  85da7d68f1eff606608bd46df43a8f57436a0131 (commit)
       via  d4eda56344420aafa49d1c0d91d1a6a95c7a4707 (commit)
       via  bd8ac0ce36dee77dc4865239576745db7588db60 (commit)
       via  98d515d5e653a75762b5da424555ac301e7f9af5 (commit)
       via  4a15ebc6f38bf2b2fe66a069a58f22f89e8ec6fc (commit)
       via  8f149bd04986ba2542e86c7ce77e5d0e92df16ea (commit)
       via  201672389bfa78c8eb59e122f6f117d1eb0644ed (commit)
       via  fc882313210b117eeec371b33796a54a4094c1d3 (commit)
       via  82d0b7f2ab2fcca6daafdcd5aae06cc0efcd2ffd (commit)
       via  2205f81d523ba1175614c6a7ee54904b74ea1a43 (commit)
       via  a9c8b8f3cd7ae57265efd4e01c51cbb4526c038f (commit)
       via  da5c7b1faa2363bbca6db163364e6e0073bcc3ab (commit)
       via  823f41b44713d161cbba7456914387e323595220 (commit)
       via  0e90f478241569b4c63e86eea31553973bdab394 (commit)
       via  0232cbe00f15efbe750b21c80973feb220bdc6c2 (commit)
       via  67eb8ca010320d7c942ba154d70456d8e9981fa7 (commit)
       via  0b722ec0d7813e933db3919df492dccf11a3b4eb (commit)
       via  b0732ce0447db537b3d643735f16aad09ddd4c7c (commit)
       via  1c2b85d7d1c1448d78b8f2a9927b7952ef42e39d (commit)
       via  bef01123fb5dfaee927bc58da465109240811a18 (commit)
       via  62794b031c00451e7bec0a0b94b4a2dadb42ae0e (commit)
       via  82041829bd8cc7400d0fee9d08e4cf1091c25a70 (commit)
       via  baa1ddd9057d0f10f85faa67f92e94268189dcd1 (commit)
       via  5379cabb61c3daff05c541ab6c9675a8a3ebfed7 (commit)
       via  30cbe355d7fcccf3e06472e5fd04c9de870adbe6 (commit)
       via  2629140f12accdfa6b4de4f59e5a76b0cc10ecb6 (commit)
       via  ea8082a263cb6d2b69764da2322b2c483913e5da (commit)
       via  6c20066d5613dfda246d598f47b9e779eef9c70b (commit)
       via  2aa422106dd081138c633ad2f9a3ede0ebdbd94a (commit)
       via  14220fefc56f7970d6b8bfd316c7ee234dd416d4 (commit)
       via  34596a05508cee3e48565a4082e9f6e9b815ad41 (commit)
       via  6b045bcd1f9613e3835551cdebd2616ea8319a36 (commit)
       via  2d46b704c62b47c2313d80b5afafbb180c1b7853 (commit)
       via  ae8851eea4d83050ad2a8e17fc6b8f0b11364fdd (commit)
       via  2078a3a34f198220806f9eabcf6fa7f6f0c9288c (commit)
       via  5ee9971dc60341992cf7da807dbec32811dd14d7 (commit)
       via  025db8d7a207b39174362981868742f1b80bdf69 (commit)
       via  00845ee8d7366b80e23e349b7cf8cd9d52551785 (commit)
       via  5d2a22ac8f92c53a89a5f4507ac3946529b9d49e (commit)
       via  4323bf82f07269a4f99a951b659749a5a1234ef2 (commit)
       via  fb95fbd37114f9c8fed764979cc5dc9124e3439c (commit)
       via  96e4248d61ccbcd23c7563f0a36d5443b607ea02 (commit)
       via  ae5693b89371208d5d5d8e884b52362f861c633f (commit)
       via  778e6009722520325aaa1efc98240fffc8f6b6d1 (commit)
       via  59b176e95540aacd2b0f167cba350d7fa18771db (commit)
       via  b52e736b9e5079aedf5252f04d019a80292bfc41 (commit)
       via  9b0a73f8a2523480f538747d579c940534ac5ac5 (commit)
       via  36fea252b03a38a0a86b830bc6a3373a88966413 (commit)
       via  d4bd5936f52d20ba21865858861dc358f531da04 (commit)
       via  19f2ceb60860badfc183f9d4e7d54ac9105ee296 (commit)
       via  ce6444b69feb909e5c8212e7335eeca14a2b11ea (commit)
       via  33ba1827b513aa735a483f6927667a9e872a2207 (commit)
       via  23035066d0c07c7b83120d3cba9efc1febe18ff5 (commit)
       via  080d47deb3088adddd96434acfd1c82bd14779c9 (commit)
       via  9f5102a30e4cb8da4697b3561206957b9a2aae05 (commit)
       via  0758ead7f4044e0867d4f953427b6c10f7afab45 (commit)
       via  9ab72138d43cb21a620c021a24e6a248b3dd77ab (commit)
       via  ffe973e3df51ab36e17087eda23b1042500d39b5 (commit)
       via  857d7fbf031d29b5b66ebe38fec725dde35008fd (commit)
       via  514b87c307fc834f39f0be155fc9bac9a6508d70 (commit)
       via  6d28690c67369927bb0806618f23533a1457eb6f (commit)
       via  029be672c7bf1b936859e811a17d45d04215ecd4 (commit)
       via  1a0577f9f348a74eacc959e091756f0e7a71accb (commit)
       via  5ec876c98d10dfdb0675ffcde709b5ce9871465b (commit)
       via  09dc37b8319af571d2e7afb0bf5b6da82cd174f6 (commit)
       via  12b24545cb9aeb86229694cda68c71577587f125 (commit)
       via  0a14dac0417402193dbcd50cb73360aeef9aabb2 (commit)
       via  9cb41a2f06b35290e72eccc8246d8abf57f6e929 (commit)
       via  7383457001cd2b6d78ec755f1cf20fa4529a43ab (commit)
       via  e6c05e218034a214844e1d4ab8b8c1060ec05489 (commit)
       via  b5735838a44da418d7d66724c8bb53c2d4d1b3c7 (commit)
       via  e4a823ad343ed077f88da1e581d0486c9b634a35 (commit)
       via  6c738ab17c6b6632f1a607cea6cea480fd1c72aa (commit)
       via  7eee12217563565248904b7051a6d68f2374ebe0 (commit)
       via  b5aa1d1b12636ba96e3fff48e27fb6acf6e38333 (commit)
       via  2fc0ce200dfa85a5f7baa577750b3c3fe8032f7d (commit)
       via  d33dd125553aa9702e1a29a6c0e30da9ffffe53b (commit)
       via  e17ddc8b0f6726a185e982c22dbf787351c22cdd (commit)
       via  8960a571bc2db7129d4ce8861f83808fb71dc81c (commit)
       via  3ff7531f6be864ca6965a60ef1e2744e945a3569 (commit)
       via  786673c6998d886be73950a99a0292344b750cea (commit)
       via  fe40dade09da3ca910f034e650db2a3d5a93a49b (commit)
       via  fb8433b523f854b3978ef1c868d573e172a5a76c (commit)
       via  5d2f35c760be8fe9aa54f566461ca8a9c5d0db78 (commit)
       via  5d8d1e296a4eccc34ab0c19acfa9a4f39a63745c (commit)
       via  08dec0f8e0129ed43373dca3a335d752a3fb5e2c (commit)
       via  6ed60d209f8c6d7d7d4978b19a2feb6170e2c67a (commit)
       via  448686bfad7694acde8b08355dd5921efd4bb2ca (commit)
       via  5deb6bbc2df15dc20f36727f0abeaa4d4ad0c17e (commit)
       via  e44d06354aa0785cf5bd247358e59f7dfe69f5ad (commit)
       via  17cd3618c21b12d7867d9e5e4dba81c1cee46cb6 (commit)
       via  376b3b0760c5198dbf5ccf740c666ed9c9acaf45 (commit)
       via  4beebdd9209a2a75b33068f4d1811b2664b957db (commit)
       via  db55f102b30e76b72b134cbd77bd183cd01f95c0 (commit)
       via  cb9f017de088330bd6e4107bad642b5e4ea66ce9 (commit)
       via  9e6e821c0a33aab0cd0e70e51059d9a2761f76bb (commit)
       via  daf78976a3df91027c2a82cd0a794255b60507b0 (commit)
       via  291591772d83ff6b5ec9412b03624d2c9f13dc82 (commit)
       via  28a43fa59ffd4f772104a76e3636b8c54b329aa3 (commit)
       via  9065335de2f05d3bc1d874b933ba80b51feb68f6 (commit)
       via  caf95c197aa4abb916d8e27d3a5004ebed2e6108 (commit)
       via  a32568b7b3006315e79ee411b50db8cc4dc435a7 (commit)
       via  3c702b8966081eb2d73914754abd247350957bdd (commit)
       via  c645605f43d14ed37a22369c3f5ac177acdeb7cd (commit)
       via  8b1a0145b79147510ccde00b28e5124b8951e4d1 (commit)
       via  19bd1a5a0b222ac2473793d87539f76a34cb1bec (commit)
       via  27e7210b344cea5c7098bf233481d5f2bf7cac97 (commit)
       via  230078e5f45f29fec72e35a77e2fd897fb6d3fb7 (commit)
       via  77b42eabd7630b5ba7f5f9fa383de73e0e7fe49d (commit)
       via  31a4bc5aacc85f79416c6c4da2e33e642077e9f9 (commit)
       via  89f331dff577268f90d885dc871cd2a152940ef8 (commit)
       via  c6c569cae91e19aa6267081ef9926f52b87d903d (commit)
       via  c8dcb23d366d559cb685b64514f49d7222df3335 (commit)
       via  3ac15510bb80c1f7fe76779dfa9ff254eb5cc796 (commit)
       via  e1c2233fab06d393eed6e16fc09f9bf672963112 (commit)
       via  5f8c941e31630163287b5197b6185dae9b03de13 (commit)
       via  fb95fe7735c5995f727ea42dfafcad820a3764da (commit)
       via  61c303c8f15dce6a985e5d0164a50844b6c64558 (commit)
       via  6eb4014782afae7660dbdc291c315a211e5ceed5 (commit)
       via  bdb56ee0da86148611e50095c9d70dfe4e4f4b7f (commit)
       via  17641de21f0430981b554af5595711a3bcf2d699 (commit)
       via  2039944b9b5a639d61466c197660a81f047013b3 (commit)
       via  2fa6a93d7f49f8073bc919620afc2cba4c7c0ce3 (commit)
       via  357c297f73a57539c9b90f7f80c4bebbdf2f8db8 (commit)
       via  d8a4ee26573da0832ad2efc424e6c9db9760fb85 (commit)
       via  c661badf19c5e9593d8c4ba719b86cd96b85f787 (commit)
       via  fed1aab5a0f813c41637807f8c0c5f8830d71942 (commit)
       via  bd8497df6615f58b2a6de701cf2a8f02c4d57d9b (commit)
       via  a5aab271f44cce29af4344ae8c24da02167deb05 (commit)
       via  57b40ee87d9689e6479d7fe4a2cf9903db3a3644 (commit)
       via  2156cc8503238162e22f418632837003593c8e3c (commit)
       via  61386c685763040bfcb779f0224bf6867a4dceb9 (commit)
       via  44bd04d531d1881d7c9a8fea9bce4b6c10aea16d (commit)
       via  ef19026395320447f81738be3fd840e278ff8da2 (commit)
       via  7299bfac202b013e968ff19c1c3109d78745ed22 (commit)
       via  bf53fbd4e92ae835280d49fbfdeeebd33e0ce3f2 (commit)
       via  f64ff7004ddec6c81538b2fc5d6827de6b79cfb8 (commit)
       via  8d844f040f2f764f7be009c17d25b06e159b1924 (commit)
       via  4a4642ad16734522436855cfd8e4d09c5ce32d06 (commit)
       via  a878a8107d48c5b1c476930cd4eb769f609fd609 (commit)
       via  8474c4bf697ccf594ca3d08dd7b1f327a44e5e82 (commit)
       via  71c5b61bcf71fb8ef3382569ea0eb059b0cca430 (commit)
       via  667d94d93788e24158ebbec640f006f49b7d438c (commit)
       via  15f662d68bbce471df63609cf8deccb280670048 (commit)
       via  96c1a3cc7becc69e4bb24cd3e40aaa605e3d90e3 (commit)
       via  774a4996dafc7a5809ab0ae08851d412e996e970 (commit)
       via  c864447b8ec187264fac032be1561c77bb3103b6 (commit)
       via  95483cb529b42683002528186eb29d6854e7ce0e (commit)
       via  fdce9eb73133bb52609ed6ad51fdb77b28447eb3 (commit)
       via  b0bc2d360163be4126f6a88b73a3c17a18714736 (commit)
       via  772ce4c2888583e2fe1e9959c705608f6d1b7e98 (commit)
       via  00ae760dd4d30c5944dfb70a892c15004a1f07ee (commit)
       via  94cb9c2b880da087af403da4b5b765ccd77ad594 (commit)
       via  7e61e0a0c0d3b87a7e1033a353a273b1502caa8d (commit)
       via  9394663ad4f49908d99dc2d2a47ddc0a72871f4a (commit)
       via  1b69710afed58b9f7ebff61229f3f29e487f4026 (commit)
       via  4d6495aa80132d3a67ad4ef9aa76d75683772a80 (commit)
       via  530e62ba1a41f63a8dbcd0838fb5cfa95cb3816a (commit)
       via  ed7622df90ee865b5be03b0d13ed5977466bebfc (commit)
       via  72beaf8964363734d4f5e7b4b11ae7b8b4b5557c (commit)
       via  b07567633570ebffebb13e92ae71d429967a7cb8 (commit)
       via  46cf8771c26b220b60ca8f30332442b5a7dbcbef (commit)
       via  593eacf16fe69fade5eeb71704ac25c6e046421b (commit)
       via  393c70b7a308f5d816a87cf21e017351dbf09260 (commit)
       via  c980c37cce2a445ec40f2c526cd89310cf7c4941 (commit)
       via  44f6dc45fffe9a44d8700bbb601dab169568f7ac (commit)
       via  4114ac95b51b5551e38bc277860abf14775cc817 (commit)
       via  16bdc3898aa70d2145872a5277f60711c32f2bd9 (commit)
       via  b40cd6851ace87bc97a095cf76b2a0ddee3666ed (commit)
       via  16cc994d2f3e15180fc022976062d616b0c05f38 (commit)
       via  870ee769dedab453f6fb65082884cd2b45d1ca14 (commit)
       via  2ad9f33c18a74df23e3ca10fd5246289093e2768 (commit)
       via  079e629bdec110c1b07376645361fbcb2e467043 (commit)
       via  165e2775424f0377b5eb8f574d83c0d0dcd67a4b (commit)
       via  34cb2bf92262addf0fb401365c23773dee3f3e89 (commit)
       via  c55cdfd6318072c9ae7005298fca07615065c97a (commit)
       via  be90b92cbe913d2eda9b95f18c099669d0a72a57 (commit)
       via  6ce0831748423ab48611785f29ee999beeaf8322 (commit)
       via  32b6b02bc06bbf0303639964f3544e9cba4b138d (commit)
       via  e632c67d9f527acf7cea58af306057e5ba5601a4 (commit)
       via  8ea9bddd35ab3a5716a51310ccc4cc6a3d96b572 (commit)
       via  318530edbf905ff288f527ef73bb88da4c57b548 (commit)
      from  c74f64796e9901c453ab7d4e509d736d037884fd (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 ff5861d1c7d7a689d775b251086d12032fcabd54
Author: Stephen Morris <stephen at isc.org>
Date:   Thu Jan 10 19:40:58 2013 +0000

    [2559] Remove elements with empty values from database access string

commit aae75525fe351f4df368402e7e05aa0e52b234b1
Author: Stephen Morris <stephen at isc.org>
Date:   Thu Jan 10 15:30:10 2013 +0000

    [2559] Add some debugging and error messages to the DHCP4/6 servers

commit 5267d959d14763af2a7c938a136267655722cd77
Author: Stephen Morris <stephen at isc.org>
Date:   Thu Jan 10 13:16:22 2013 +0000

    [2559] Move Database Access parser to dhcpsrv

commit 6a47ad417ae15c4cc9bad53347ae06088a642ec3
Merge: c74f647 1c54eac
Author: Stephen Morris <stephen at isc.org>
Date:   Thu Jan 10 13:02:39 2013 +0000

    [2559] Merge branch 'master' into trac2559
    
    Conflicts:
    	src/bin/dhcp6/config_parser.cc
    
    Pick up relocation of the base parser header file to dhcpsrv,
    and the correction to the getting of a full configuration
    (ticket 2619).

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

Summary of changes:
 .gitignore                                         |    3 +-
 ChangeLog                                          |  116 +-
 README                                             |   24 +-
 configure.ac                                       |   70 +-
 doc/guide/bind10-guide.xml                         |   42 +-
 examples/README                                    |    2 +-
 m4macros/ax_boost_for_bind10.m4                    |  112 ++
 src/bin/auth/auth_messages.mes                     |    8 +-
 src/bin/auth/b10-auth.xml                          |   48 +-
 src/bin/auth/tests/.gitignore                      |    2 +
 src/bin/auth/tests/Makefile.am                     |   38 +-
 src/bin/auth/tests/gen-query-testdata.py           |   98 ++
 src/bin/auth/tests/query_inmemory_unittest.cc      |  123 ---
 src/bin/auth/tests/query_unittest.cc               | 1108 ++++++++++++--------
 src/bin/auth/tests/testdata/.gitignore             |   10 +
 src/bin/auth/tests/testdata/Makefile.am            |    7 +-
 src/bin/auth/tests/testdata/example-base-inc.zone  |  236 +++++
 src/bin/auth/tests/testdata/example-base.zone.in   |    7 +
 .../testdata/example-common-inc-template.zone      |    5 +
 src/bin/auth/tests/testdata/example-nsec3-inc.zone |   16 +
 src/bin/auth/tests/testdata/example-nsec3.zone.in  |    8 +
 src/bin/auth/tests/testdata/example.zone           |  121 ---
 src/bin/auth/tests/testdata/example.zone.in        |    6 +
 src/bin/bind10/bind10.xml                          |    2 +-
 src/bin/bind10/bind10_messages.mes                 |   11 +-
 src/bin/bind10/bind10_src.py.in                    |   79 +-
 src/bin/bind10/tests/bind10_test.py.in             |   97 +-
 src/bin/cfgmgr/b10-cfgmgr.xml                      |    2 +-
 src/bin/cmdctl/b10-certgen.xml                     |    2 +-
 src/bin/cmdctl/b10-cmdctl.xml                      |   12 +-
 src/bin/dbutil/dbutil_messages.mes                 |    6 +-
 src/bin/dhcp4/Makefile.am                          |    2 +-
 src/bin/dhcp4/config_parser.cc                     |  952 ++++++++++++++---
 src/bin/dhcp4/config_parser.h                      |  117 +--
 src/bin/dhcp4/dhcp4.spec                           |   78 +-
 src/bin/dhcp4/dhcp4_messages.mes                   |   64 +-
 src/bin/dhcp4/tests/Makefile.am                    |    6 +-
 src/bin/dhcp4/tests/config_parser_unittest.cc      |  575 +++++++++-
 src/bin/dhcp6/Makefile.am                          |   13 +-
 src/bin/dhcp6/config_parser.cc                     |  808 +++++++++-----
 src/bin/dhcp6/config_parser.h                      |  124 +--
 src/bin/dhcp6/dhcp6.dox                            |   32 +-
 src/bin/dhcp6/dhcp6.spec                           |   10 +
 src/bin/dhcp6/dhcp6_messages.mes                   |  109 +-
 src/bin/dhcp6/dhcp6_srv.cc                         |  187 +++-
 src/bin/dhcp6/dhcp6_srv.h                          |   39 +-
 src/bin/dhcp6/tests/Makefile.am                    |   11 +-
 src/bin/dhcp6/tests/config_parser_unittest.cc      |  102 +-
 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc          |  380 +++++--
 src/bin/loadzone/.gitignore                        |    1 +
 src/bin/loadzone/b10-loadzone.xml                  |    2 +-
 src/bin/loadzone/run_loadzone.sh.in                |   10 +-
 src/bin/loadzone/tests/correct/known.test.out      |    4 +-
 src/bin/msgq/Makefile.am                           |   13 +-
 src/bin/msgq/msgq.py.in                            |   55 +-
 src/bin/msgq/msgq.xml                              |    2 +-
 src/bin/msgq/msgq_messages.mes                     |   88 ++
 src/bin/msgq/run_msgq.sh.in                        |   18 +-
 src/bin/msgq/tests/Makefile.am                     |    2 +
 src/bin/msgq/tests/msgq_test.in                    |   28 -
 src/bin/msgq/tests/msgq_test.py                    |    3 +
 src/bin/resolver/b10-resolver.xml                  |    4 +-
 src/bin/resolver/resolver.h                        |    6 +-
 src/bin/stats/b10-stats-httpd.xml                  |    8 +-
 src/bin/stats/b10-stats.xml                        |    2 +-
 src/bin/xfrin/xfrin_messages.mes                   |   12 +-
 src/bin/xfrout/xfrout_messages.mes                 |    7 +-
 src/lib/cc/data.cc                                 |    4 +-
 src/lib/cc/data.h                                  |    3 +-
 src/lib/config/config_data.cc                      |    2 +-
 src/lib/config/config_data.h                       |   16 +-
 src/lib/config/tests/config_data_unittests.cc      |   11 +-
 src/lib/datasrc/database.cc                        |    2 +-
 src/lib/datasrc/datasrc_messages.mes               |    8 +-
 .../tests/memory/rdata_serialization_unittest.cc   |    3 +
 .../datasrc/tests/zone_finder_context_unittest.cc  |   32 +-
 src/lib/dhcp/Makefile.am                           |   12 +-
 src/lib/dhcp/duid.cc                               |   10 +
 src/lib/dhcp/duid.h                                |    3 +
 src/lib/dhcp/libdhcp++.cc                          |   28 +
 src/lib/dhcp/libdhcp++.h                           |   15 +
 src/lib/dhcp/option_definition.cc                  |    8 +-
 src/lib/dhcp/option_definition.h                   |   15 +-
 src/lib/dhcp/tests/duid_unittest.cc                |   12 +-
 src/lib/dhcp/tests/libdhcp++_unittest.cc           |   60 ++
 src/lib/dhcpsrv/Makefile.am                        |   27 +-
 src/lib/dhcpsrv/cfgmgr.cc                          |  141 ++-
 src/lib/dhcpsrv/cfgmgr.h                           |  101 +-
 src/{bin/dhcp6 => lib/dhcpsrv}/dbaccess_parser.cc  |   18 +-
 src/{bin/dhcp6 => lib/dhcpsrv}/dbaccess_parser.h   |    2 +-
 .../dhcpsrv/dhcp_config_parser.h}                  |  111 +-
 .../dhcp4_log.cc => lib/dhcpsrv/dhcpsrv_log.cc}    |    6 +-
 src/lib/dhcpsrv/dhcpsrv_log.h                      |   66 ++
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |  231 ++++
 .../hwaddr.cc}                                     |   32 +-
 .../hwaddr.h}                                      |   36 +-
 src/lib/dhcpsrv/lease_mgr.cc                       |   14 +-
 src/lib/dhcpsrv/lease_mgr.h                        |   20 +-
 src/lib/dhcpsrv/lease_mgr_factory.cc               |   53 +-
 src/lib/dhcpsrv/lease_mgr_factory.h                |   35 +-
 src/lib/dhcpsrv/memfile_lease_mgr.cc               |   71 +-
 src/lib/dhcpsrv/memfile_lease_mgr.h                |    1 +
 src/lib/dhcpsrv/mysql_lease_mgr.cc                 |   49 +-
 src/lib/dhcpsrv/mysql_lease_mgr.h                  |    1 +
 src/lib/dhcpsrv/option_space.cc                    |   71 ++
 src/lib/dhcpsrv/option_space.h                     |  189 ++++
 src/lib/dhcpsrv/subnet.cc                          |   65 +-
 src/lib/dhcpsrv/subnet.h                           |   47 +-
 src/lib/dhcpsrv/tests/Makefile.am                  |    5 +
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |  255 ++++-
 .../dhcpsrv}/tests/dbaccess_parser_unittest.cc     |   59 +-
 .../tests/hwaddr_unittest.cc}                      |   39 +-
 .../dhcpsrv/tests/lease_mgr_factory_unittest.cc    |  135 ++-
 src/lib/dhcpsrv/tests/option_space_unittest.cc     |  150 +++
 src/lib/dhcpsrv/tests/subnet_unittest.cc           |  116 +-
 src/lib/dns/Makefile.am                            |   10 +-
 .../dns/{python/nsec3hash_python.h => dns_fwd.h}   |   59 +-
 src/lib/dns/master_loader.cc                       |   23 +-
 src/lib/dns/master_loader_callbacks.h              |    6 +-
 src/lib/dns/python/Makefile.am                     |    5 +
 src/lib/dns/python/pydnspp.cc                      |   21 +-
 src/lib/dns/python/rrset_collection_python.cc      |  426 ++++++++
 .../python/rrset_collection_python.h}              |   40 +-
 src/lib/dns/python/rrset_collection_python_inc.cc  |  148 +++
 src/lib/dns/python/tests/Makefile.am               |    2 +
 .../python/tests/rrset_collection_python_test.py   |  140 +++
 .../dns/python/tests/zone_checker_python_test.py   |  179 ++++
 src/lib/dns/python/zone_checker_python.cc          |  224 ++++
 .../python/zone_checker_python.h}                  |   17 +-
 src/lib/dns/python/zone_checker_python_inc.cc      |   79 ++
 src/lib/dns/rdata.h                                |    2 +-
 src/lib/dns/rdata/generic/detail/char_string.cc    |   27 +-
 src/lib/dns/rdata/generic/detail/char_string.h     |   19 +-
 src/lib/dns/rdata/generic/detail/txt_like.h        |   10 +-
 src/lib/dns/rrclass-placeholder.h                  |   55 +-
 src/lib/dns/rrclass.cc                             |   17 +-
 src/lib/dns/rrcollator.cc                          |    2 +-
 src/lib/dns/rrparamregistry-placeholder.cc         |   35 +-
 src/lib/dns/rrparamregistry.h                      |   45 +-
 src/lib/dns/rrset_collection.cc                    |  128 +++
 src/lib/dns/rrset_collection.h                     |  172 +++
 src/lib/dns/rrset_collection_base.h                |  163 +++
 src/lib/dns/rrttl.h                                |    2 +-
 src/lib/dns/rrtype.cc                              |    7 +-
 src/lib/dns/tests/Makefile.am                      |    2 +
 src/lib/dns/tests/master_loader_unittest.cc        |    3 +-
 src/lib/dns/tests/rdata_char_string_unittest.cc    |   59 +-
 src/lib/dns/tests/rdata_txt_like_unittest.cc       |   18 +
 src/lib/dns/tests/rrclass_unittest.cc              |   10 +-
 src/lib/dns/tests/rrset_collection_unittest.cc     |  246 +++++
 src/lib/dns/tests/zone_checker_unittest.cc         |  352 +++++++
 src/lib/dns/zone_checker.cc                        |  196 ++++
 src/lib/dns/zone_checker.h                         |  162 +++
 src/lib/log/log_messages.mes                       |    4 +-
 src/lib/log/logger_manager_impl.cc                 |   10 +
 src/lib/python/isc/bind10/component.py             |   10 +-
 src/lib/python/isc/bind10/special_component.py     |   15 +-
 src/lib/python/isc/bind10/tests/component_test.py  |   32 +-
 src/lib/python/isc/datasrc/tests/.gitignore        |    1 +
 src/lib/python/isc/ddns/libddns_messages.mes       |    2 +-
 src/lib/python/isc/log_messages/Makefile.am        |    2 +
 src/lib/python/isc/log_messages/msgq_messages.py   |    1 +
 src/lib/resolve/resolve_messages.mes               |    6 +-
 src/lib/server_common/server_common_messages.mes   |    4 +-
 src/lib/testutils/dnsmessage_test.h                |    3 +-
 src/lib/util/encode/base_n.cc                      |    3 +-
 tests/system/bindctl/setup.sh                      |    2 +-
 tests/system/bindctl/tests.sh                      |    4 +
 tests/system/conf.sh.in                            |    5 +-
 tests/system/glue/setup.sh.in                      |    8 +-
 tests/system/ixfr/in-1/setup.sh.in                 |    2 +-
 tests/system/ixfr/in-2/setup.sh.in                 |    2 +-
 tests/system/ixfr/in-3/setup.sh.in                 |    2 +-
 tests/system/ixfr/in-4/setup.sh.in                 |    2 +-
 tests/tools/perfdhcp/tests/.gitignore              |    5 +
 175 files changed, 9687 insertions(+), 2234 deletions(-)
 create mode 100644 m4macros/ax_boost_for_bind10.m4
 create mode 100755 src/bin/auth/tests/gen-query-testdata.py
 delete mode 100644 src/bin/auth/tests/query_inmemory_unittest.cc
 create mode 100644 src/bin/auth/tests/testdata/example-base-inc.zone
 create mode 100644 src/bin/auth/tests/testdata/example-base.zone.in
 create mode 100644 src/bin/auth/tests/testdata/example-common-inc-template.zone
 create mode 100644 src/bin/auth/tests/testdata/example-nsec3-inc.zone
 create mode 100644 src/bin/auth/tests/testdata/example-nsec3.zone.in
 delete mode 100644 src/bin/auth/tests/testdata/example.zone
 create mode 100644 src/bin/auth/tests/testdata/example.zone.in
 create mode 100644 src/bin/msgq/msgq_messages.mes
 delete mode 100755 src/bin/msgq/tests/msgq_test.in
 rename src/{bin/dhcp6 => lib/dhcpsrv}/dbaccess_parser.cc (86%)
 rename src/{bin/dhcp6 => lib/dhcpsrv}/dbaccess_parser.h (99%)
 copy src/{bin/dhcp6/config_parser.h => lib/dhcpsrv/dhcp_config_parser.h} (50%)
 copy src/{bin/dhcp4/dhcp4_log.cc => lib/dhcpsrv/dhcpsrv_log.cc} (86%)
 create mode 100644 src/lib/dhcpsrv/dhcpsrv_log.h
 create mode 100644 src/lib/dhcpsrv/dhcpsrv_messages.mes
 copy src/lib/{dns/master_loader_callbacks.cc => dhcpsrv/hwaddr.cc} (62%)
 copy src/lib/{dns/master_loader_callbacks.cc => dhcpsrv/hwaddr.h} (56%)
 create mode 100644 src/lib/dhcpsrv/option_space.cc
 create mode 100644 src/lib/dhcpsrv/option_space.h
 rename src/{bin/dhcp6 => lib/dhcpsrv}/tests/dbaccess_parser_unittest.cc (85%)
 copy src/lib/{xfr/tests/client_test.cc => dhcpsrv/tests/hwaddr_unittest.cc} (50%)
 create mode 100644 src/lib/dhcpsrv/tests/option_space_unittest.cc
 copy src/lib/dns/{python/nsec3hash_python.h => dns_fwd.h} (52%)
 create mode 100644 src/lib/dns/python/rrset_collection_python.cc
 copy src/lib/{python/isc/acl/dns_requestcontext_python.h => dns/python/rrset_collection_python.h} (55%)
 create mode 100644 src/lib/dns/python/rrset_collection_python_inc.cc
 create mode 100644 src/lib/dns/python/tests/rrset_collection_python_test.py
 create mode 100644 src/lib/dns/python/tests/zone_checker_python_test.py
 create mode 100644 src/lib/dns/python/zone_checker_python.cc
 copy src/lib/{python/isc/datasrc/zone_loader_python.h => dns/python/zone_checker_python.h} (74%)
 create mode 100644 src/lib/dns/python/zone_checker_python_inc.cc
 create mode 100644 src/lib/dns/rrset_collection.cc
 create mode 100644 src/lib/dns/rrset_collection.h
 create mode 100644 src/lib/dns/rrset_collection_base.h
 create mode 100644 src/lib/dns/tests/rrset_collection_unittest.cc
 create mode 100644 src/lib/dns/tests/zone_checker_unittest.cc
 create mode 100644 src/lib/dns/zone_checker.cc
 create mode 100644 src/lib/dns/zone_checker.h
 create mode 100644 src/lib/python/isc/log_messages/msgq_messages.py

-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index dd6903c..7bc41b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,5 +34,6 @@ TAGS
 /all.info
 /coverage-cpp-html
 /dns++.pc
-/report.info
+/local.zone.sqlite3
 /logger_lockfile
+/report.info
diff --git a/ChangeLog b/ChangeLog
index 977b6ea..840da9a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,98 @@
+543.	[func]*		jelte
+	When calling getFullConfig() as a module, , the configuration is now
+	returned as properly-structured JSON.  Previously, the structure had
+	been flattened, with all data being labelled by fully-qualified element
+	names.
+	(Trac #2619, git bed3c88c25ea8f7e951317775e99ebce3340ca22)
+
+542.	[func]		marcin
+	Created OptionSpace and OptionSpace6 classes to represent DHCP
+	option spaces. The option spaces are used to group instances
+	and definitions of options having uniqe codes. A special type
+	of option space is the so-called "vendor specific option space"
+	which groups sub-options sent within Vendor Encapsulated Options.
+	The new classes are not used yet but they will be used once
+	the creation of option spaces by configuration manager is
+	implemented.
+	(Trac #2313, git 37a27e19be874725ea3d560065e5591a845daa89)
+
+541.	[func]		marcin
+	Added routines to search for configured DHCP options and their
+	definitions using name of the option space they belong to.
+	New routines are called internally from the DHCPv4 and DHCPv6
+	servers code.
+	(Trac #2315, git 741fe7bc96c70df35d9a79016b0aa1488e9b3ac8)
+
+540.	[func]		marcin
+	DHCP Option values can be now specified using a string of
+	tokens separated with comma sign. Subsequent tokens are used
+	to set values for corresponding data fields in a particular
+	DHCP option. The format of the token matches the data type
+	of the corresponding option field: e.g. "192.168.2.1" for IPv4
+	address, "5" for integer value etc.
+	(Trac #2545, git 792c129a0785c73dd28fd96a8f1439fe6534a3f1)
+
+539.	[func]		stephen
+	Add logging to the DHCP server library.
+	(Trac #2524, git b55b8b6686cc80eed41793c53d1779f4de3e9e3c)
+
+538.	[bug]		muks
+	Added escaping of special characters (double-quotes, semicolon,
+	backslash, etc.) in text-like RRType's toText() implementation.
+	Without this change, some TXT and SPF RDATA were incorrectly
+	stored in SQLite3 datasource as they were not escaped.
+	(Trac #2535, git f516fc484544b7e08475947d6945bc87636d4115)
+
+537.	[func]		tomek
+	b10-dhcp6: Support for RELEASE message has been added. Clients
+	are now able to release their non-temporary IPv6 addresses.
+	(Trac #2326, git 0974318566abe08d0702ddd185156842c6642424)
+
+536.	[build]		jinmei
+	Detect a build issue on FreeBSD with g++ 4.2 and Boost installed via
+	FreeBSD ports at ./configure time.  This seems to be a bug of
+	FreeBSD	ports setup and has been reported to the maintainer:
+	http://www.freebsd.org/cgi/query-pr.cgi?pr=174753
+	Until it's fixed, you need to build BIND 10 for FreeBSD that has
+	this problem with specifying --without-werror, with clang++
+	(development version), or with manually extracted Boost header
+	files (no compiled Boost library is necessary).
+	(Trac #1991, git 6b045bcd1f9613e3835551cdebd2616ea8319a36)
+
+535.	[bug]		jelte
+	The log4cplus internal logging mechanism has been disabled, and no
+	output from the log4cplus library itself should be printed to
+	stderr anymore. This output can be enabled by using the
+	compile-time option --enable-debug.
+	(Trac #1081, git db55f102b30e76b72b134cbd77bd183cd01f95c0)
+
+534.	[func]*			vorner
+	The b10-msgq now uses the same logging format as the rest
+	of the system. However, it still doesn't obey the common
+	configuration, as due to technical issues it is not able
+	to read it yet.
+	(git 9e6e821c0a33aab0cd0e70e51059d9a2761f76bb)
+
+bind10-1.0.0-beta released on December 20, 2012
+
+533.	[build]*		jreed
+	Changed the package name in configure.ac from bind10-devel
+	to bind10. This means the default sub-directories for
+	etc, include, libexec, share, share/doc, and var are changed.
+	If upgrading from a previous version, you may need to move
+	and update your configurations or change references for the
+	old locations.
+	(git bf53fbd4e92ae835280d49fbfdeeebd33e0ce3f2)
+
+532.	[func]		marcin
+	Implemented configuration of DHCPv4 option values using
+	the configuration manager. In order to set values for the
+	data fields carried by a particular option, the user
+	specifies a string of hexadecimal digits that is converted
+	to binary data and stored in the option buffer. A more
+	user-friendly way of specifying option content is planned.
+	(Trac #2544, git fed1aab5a0f813c41637807f8c0c5f8830d71942)
+
 531.	[func]		tomek
 	b10-dhcp6: Added support for expired leases. Leases for IPv6
 	addresses that are past their valid lifetime may be recycled, i.e.
@@ -19,16 +114,17 @@
 	(Trac #2380, git 689b015753a9e219bc90af0a0b818ada26cc5968)
 
 529.	[func]*		team
-	The in-memory data source now uses a more complete master file
-	parser to load textual zone files.  As of this change it supports
-	multi-line RR representation and more complete support for escaped
-	and quoted strings.  It also produces more helpful log when there
-	is an error in the zone file.  It will be enhanced as more
-	specific tasks in the #2368 meta ticket are completed.  The new
-	parser is generally upper compatible to the previous one, but due
-	to the tighter checks some input that has been accepted so far
-	could now be rejected, so it's advisable to check if you use
-	textual zone files directly loaded to memory.
+	The in-memory data source now uses a more complete master
+	file parser to load textual zone files.  As of this change
+	it supports multi-line RR representation and more complete
+	support for escaped and quoted strings.  It also produces
+	more helpful log messages when there is an error in the zone
+	file.  It will be enhanced as more specific tasks in the
+	#2368 meta ticket are completed.  The new parser is generally
+	backward compatible to the previous one, but due to the
+	tighter checks some input that has been accepted so far
+	could now be rejected, so it's advisable to check if you
+	use textual zone files directly loaded to memory.
 	(Trac #2470, git c4cf36691115c15440b65cac16f1c7fcccc69521)
 
 528.	[func]		marcin
diff --git a/README b/README
index fdc11a1..306b7d8 100644
--- a/README
+++ b/README
@@ -7,16 +7,20 @@ DHCP. BIND 10 is written in C++ and Python and provides a modular
 environment for serving, maintaining, and developing DNS and DHCP.
 
 This release includes the bind10 master process, b10-msgq message
-bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
-backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl
-remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
-AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
-secondary manager, b10-stats statistics collection and reporting
-daemon, b10-stats-httpd for HTTP access to XML-formatted stats,
-b10-host DNS lookup utility, and a new libdns++ library for C++
-with a python wrapper. BIND 10 also provides experimental DHCPv4
-and DHCPv6 servers, b10-dhcp4 and b10-dhcp6, a portable DHCP library,
-libdhcp++, and a DHCP benchmarking tool, perfdhcp.
+bus, b10-cmdctl remote control daemon, b10-cfgmgr configuration
+manager, b10-stats statistics collection and reporting daemon, and
+b10-stats-httpd for HTTP access to XML-formatted stats.
+
+For DNS services, it provides the b10-auth authoritative DNS server
+(with SQLite3 and in-memory backends), b10-resolver recursive or
+forwarding DNS server, b10-xfrin IXFR/AXFR inbound service, b10-xfrout
+outgoing IXFR/AXFR service, b10-zonemgr secondary manager, libdns++
+library for C++ with a python wrapper, and many tests and example
+programs.
+
+BIND 10 also provides experimental DHCPv4 and DHCPv6 servers,
+b10-dhcp4 and b10-dhcp6, a portable DHCP library, libdhcp++, and
+a DHCP benchmarking tool, perfdhcp.
 
 Documentation is included with the source. See doc/guide/bind10-guide.txt
 (or bind10-guide.html) for installation instructions.  The
diff --git a/configure.ac b/configure.ac
index 24e9b03..892bc6c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20120817, bind10-dev at isc.org)
+AC_INIT(bind10, 20121219, bind10-dev at isc.org)
 AC_CONFIG_SRCDIR(README)
 AM_INIT_AUTOMAKE([foreign])
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
@@ -166,8 +166,6 @@ fi
 
 fi				dnl GXX = yes
 
-AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-
 # allow building programs with static link.  we need to make it selective
 # because loadable modules cannot be statically linked.
 AC_ARG_ENABLE([static-link],
@@ -838,57 +836,22 @@ LIBS=$LIBS_SAVED
 #
 # Configure Boost header path
 #
-# If explicitly specified, use it.
-AC_ARG_WITH([boost-include],
-  AC_HELP_STRING([--with-boost-include=PATH],
-    [specify exact directory for Boost headers]),
-    [boost_include_path="$withval"])
-# If not specified, try some common paths.
-if test -z "$with_boost_include"; then
-	boostdirs="/usr/local /usr/pkg /opt /opt/local"
-	for d in $boostdirs
-	do
-		if test -f $d/include/boost/shared_ptr.hpp; then
-			boost_include_path=$d/include
-			break
-		fi
-	done
+AX_BOOST_FOR_BIND10
+# Boost offset_ptr is required in one library and not optional right now, so
+# we unconditionally fail here if it doesn't work.
+if test "$BOOST_OFFSET_PTR_FAILURE" = "yes"; then
+    AC_MSG_ERROR([Failed to compile a required header file.  Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror.  See the ChangeLog entry for Trac no. 2147 for more details.])
 fi
-CPPFLAGS_SAVES="$CPPFLAGS"
-if test "${boost_include_path}" ; then
-	BOOST_INCLUDES="-I${boost_include_path}"
-	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+
+# There's a known bug in FreeBSD ports for Boost that would trigger a false
+# warning in build with g++ and -Werror (we exclude clang++ explicitly to
+# avoid unexpected false positives).
+if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGPP = "no"; then
+    AC_MSG_ERROR([Failed to compile a required header file.  If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror.  See the ChangeLog entry for Trac no. 1991 for more details.])
 fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
-  AC_MSG_ERROR([Missing required header files.]))
-
-# Detect whether Boost tries to use threads by default, and, if not,
-# make it sure explicitly.  In some systems the automatic detection
-# may depend on preceding header files, and if inconsistency happens
-# it could lead to a critical disruption.
-AC_MSG_CHECKING([whether Boost tries to use threads])
-AC_TRY_COMPILE([
-#include <boost/config.hpp>
-#ifdef BOOST_HAS_THREADS
-#error "boost will use threads"
-#endif],,
-[AC_MSG_RESULT(no)
- CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
-[AC_MSG_RESULT(yes)])
-
-# Boost offset_ptr is required in one library (not optional right now), and
-# it's known it doesn't compile on some platforms, depending on boost version,
-# its local configuration, and compiler.
-AC_MSG_CHECKING([Boost offset_ptr compiles])
-AC_TRY_COMPILE([
-#include <boost/interprocess/offset_ptr.hpp>
-],,
-[AC_MSG_RESULT(yes)],
-[AC_MSG_RESULT(no)
- AC_MSG_ERROR([Failed to compile a required header file.  Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror.  See the ChangeLog entry for Trac no. 2147 for more details.])])
 
-CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
-AC_SUBST(BOOST_INCLUDES)
+# Add some default CPP flags needed for Boost, identified by the AX macro.
+CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
 
 # I can't get some of the #include <asio.hpp> right without this
 # TODO: find the real cause of asio/boost wanting pthreads
@@ -1354,10 +1317,12 @@ AC_OUTPUT([doc/version.ent
            src/bin/usermgr/run_b10-cmdctl-usermgr.sh
            src/bin/usermgr/b10-cmdctl-usermgr.py
            src/bin/msgq/msgq.py
-           src/bin/msgq/tests/msgq_test
            src/bin/msgq/run_msgq.sh
            src/bin/auth/auth.spec.pre
            src/bin/auth/spec_config.h.pre
+           src/bin/auth/tests/testdata/example.zone
+           src/bin/auth/tests/testdata/example-base.zone
+           src/bin/auth/tests/testdata/example-nsec3.zone
            src/bin/dhcp4/spec_config.h.pre
            src/bin/dhcp6/spec_config.h.pre
            src/bin/tests/process_rename_test.py
@@ -1420,7 +1385,6 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/bin/sysinfo/run_sysinfo.sh
            chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
            chmod +x src/bin/msgq/run_msgq.sh
-           chmod +x src/bin/msgq/tests/msgq_test
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/log/tests/console_test.sh
            chmod +x src/lib/log/tests/destination_test.sh
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 336cc9b..23756b4 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -503,7 +503,7 @@ var/
           </listitem>
           <listitem>
           <simpara>
-            <filename>etc/bind10-devel/</filename> —
+            <filename>etc/bind10/</filename> —
             configuration files.
           </simpara>
           </listitem>
@@ -515,7 +515,7 @@ var/
           </listitem>
           <listitem>
             <simpara>
-              <filename>libexec/bind10-devel/</filename> —
+              <filename>libexec/bind10/</filename> —
               executables that a user wouldn't normally run directly and
               are not run independently.
               These are the BIND 10 modules which are daemons started by
@@ -530,13 +530,13 @@ var/
           </listitem>
           <listitem>
             <simpara>
-              <filename>share/bind10-devel/</filename> —
+              <filename>share/bind10/</filename> —
               configuration specifications.
             </simpara>
           </listitem>
           <listitem>
             <simpara>
-              <filename>share/doc/bind10-devel/</filename> —
+              <filename>share/doc/bind10/</filename> —
               this guide and other supplementary documentation.
             </simpara>
           </listitem>
@@ -548,7 +548,7 @@ var/
           </listitem>
           <listitem>
             <simpara>
-              <filename>var/bind10-devel/</filename> —
+              <filename>var/bind10/</filename> —
               data source and configuration databases.
             </simpara>
           </listitem>
@@ -910,7 +910,7 @@ as a dependency earlier -->
         Administrators do not communicate directly with the
         <command>b10-msgq</command> daemon.
         By default, BIND 10 uses a UNIX domain socket file named
-        <filename>/usr/local/var/bind10-devel/msg_socket</filename>
+        <filename>/usr/local/var/bind10/msg_socket</filename>
         for this interprocess communication.
       </para>
 
@@ -972,7 +972,7 @@ config changes are actually commands to cfgmgr
 <!-- TODO: what about command line switch to change this? -->
       <para>
         The stored configuration file is at
-        <filename>/usr/local/var/bind10-devel/b10-config.db</filename>.
+        <filename>/usr/local/var/bind10/b10-config.db</filename>.
         (The directory is what was defined at build configure time for
         <option>--localstatedir</option>.
         The default is <filename>/usr/local/var/</filename>.)
@@ -1065,13 +1065,13 @@ but you might wanna check with likun
     <para>The HTTPS server requires a private key,
       such as a RSA PRIVATE KEY.
       The default location is at
-      <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>.
+      <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>.
       (A sample key is at
-      <filename>/usr/local/share/bind10-devel/cmdctl-keyfile.pem</filename>.)
+      <filename>/usr/local/share/bind10/cmdctl-keyfile.pem</filename>.)
       It also uses a certificate located at
-      <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>.
+      <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>.
       (A sample certificate is at
-      <filename>/usr/local/share/bind10-devel/cmdctl-certfile.pem</filename>.)
+      <filename>/usr/local/share/bind10/cmdctl-certfile.pem</filename>.)
       This may be a self-signed certificate or purchased from a
       certification authority.
     </para>
@@ -1107,11 +1107,11 @@ but that is a single file, maybe this should go back to that format?
     <para>
       The <command>b10-cmdctl</command> daemon also requires
       the user account file located at
-      <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>.
+      <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>.
       This comma-delimited file lists the accounts with a user name,
       hashed password, and salt.
       (A sample file is at
-      <filename>/usr/local/share/bind10-devel/cmdctl-accounts.csv</filename>.
+      <filename>/usr/local/share/bind10/cmdctl-accounts.csv</filename>.
       It contains the user named <quote>root</quote> with the password
       <quote>bind10</quote>.)
     </para>
@@ -1141,14 +1141,14 @@ or accounts database -->
         The configuration items for <command>b10-cmdctl</command> are:
         <varname>accounts_file</varname> which defines the path to the
         user accounts database (the default is
-        <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>);
+        <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>);
         <varname>cert_file</varname> which defines the path to the
         PEM certificate file (the default is
-        <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>);
+        <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>);
         and
 	<varname>key_file</varname> which defines the path to the
 	PEM private key file (the default is
-        <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>).
+        <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>).
       </para>
 
     </section>
@@ -1872,7 +1872,7 @@ tsig_keys/keys[0]   "example.key.:c2VjcmV0" string  (modified)
         Each rule is a name-value mapping (a dictionary, in the JSON
         terminology). Each rule must contain exactly one mapping called
         "action", which describes what should happen if the rule applies.
-        There may be more mappings, calld matches, which describe the
+        There may be more mappings, called matches, which describe the
         conditions under which the rule applies.
       </para>
 
@@ -2459,7 +2459,7 @@ can use various data source backends.
         data source — one that serves things like
         <quote>AUTHORS.BIND.</quote>. The IN class contains single SQLite3
         data source with database file located at
-        <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+        <filename>/usr/local/var/bind10/zone.sqlite3</filename>.
       </para>
 
       <para>
@@ -3673,7 +3673,7 @@ mysql></screen>
          <para>
           3. Create the database tables:
           <screen>mysql> <userinput>CONNECT kea;</userinput>
-mysql> <userinput>SOURCE <replaceable><path-to-bind10></replaceable>/share/bind10-devel/dhcpdb_create.mysql</userinput></screen>
+mysql> <userinput>SOURCE <replaceable><path-to-bind10></replaceable>/share/bind10/dhcpdb_create.mysql</userinput></screen>
         </para>
          <para>
           4. Create the user under which BIND 10 will access the database and grant it access to the database tables:
@@ -3871,7 +3871,7 @@ Dhcp6/subnet6	         []     list    (default)</screen>
     <section id="dhcp6-limit">
       <title>DHCPv6 Server Limitations</title>
       <para> These are the current limitations and known problems
-      with the the DHCPv6 server
+      with the DHCPv6 server
       software. Most of them are reflections of the early stage of
       development and should be treated as <quote>not implemented
       yet</quote>, rather than actual limitations.</para>
@@ -4156,7 +4156,7 @@ specify module-wide logging and see what appears...
           If there are multiple logger specifications in the
           configuration that might match a particular logger, the
           specification with the more specific logger name takes
-          precedence. For example, if there are entries for for
+          precedence. For example, if there are entries for
           both <quote>*</quote> and <quote>Resolver</quote>, the
           resolver module — and all libraries it uses —
           will log messages according to the configuration in the
diff --git a/examples/README b/examples/README
index 08f53fa..aa5f3c9 100644
--- a/examples/README
+++ b/examples/README
@@ -15,7 +15,7 @@ the "m4" subdirectory as a template for your own project.  The key is
 to call the AX_ISC_BIND10 function (as the sample configure.ac does)
 from your configure.ac.  Then it will check the availability of
 necessary stuff and set some corresponding AC variables.  You can then
-use the resulting variables in your Makefile.in or Makefile.ac.
+use the resulting variables in your Makefile.in or Makefile.am.
 
 If you use automake, don't forget adding the following line to the top
 level Makefile.am:
diff --git a/m4macros/ax_boost_for_bind10.m4 b/m4macros/ax_boost_for_bind10.m4
new file mode 100644
index 0000000..1ce367e
--- /dev/null
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -0,0 +1,112 @@
+dnl @synopsis AX_BOOST_FOR_BIND10
+dnl
+dnl Test for the Boost C++ header files intended to be used within BIND 10
+dnl
+dnl If no path to the installed boost header files is given via the
+dnl --with-boost-include option,  the macro searchs under
+dnl /usr/local /usr/pkg /opt /opt/local directories.
+dnl If it cannot detect any workable path for Boost, this macro treats it
+dnl as a fatal error (so it cannot be called if the availability of Boost
+dnl is optional).
+dnl
+dnl This macro also tries to identify some known portability issues, and
+dnl sets corresponding variables so the caller can react to (or ignore,
+dnl depending on other configuration) specific issues appropriately.
+dnl
+dnl This macro calls:
+dnl
+dnl   AC_SUBST(BOOST_INCLUDES)
+dnl
+dnl And possibly sets:
+dnl   CPPFLAGS_BOOST_THREADCONF should be added to CPPFLAGS by caller
+dnl   BOOST_OFFSET_PTR_WOULDFAIL set to "yes" if offset_ptr would cause build
+dnl                              error; otherwise set to "no"
+dnl   BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
+dnl                                build error; otherwise set to "no"
+dnl
+
+AC_DEFUN([AX_BOOST_FOR_BIND10], [
+AC_LANG_SAVE
+AC_LANG([C++])
+
+#
+# Configure Boost header path
+#
+# If explicitly specified, use it.
+AC_ARG_WITH([boost-include],
+  AC_HELP_STRING([--with-boost-include=PATH],
+    [specify exact directory for Boost headers]),
+    [boost_include_path="$withval"])
+# If not specified, try some common paths.
+if test -z "$with_boost_include"; then
+	boostdirs="/usr/local /usr/pkg /opt /opt/local"
+	for d in $boostdirs
+	do
+		if test -f $d/include/boost/shared_ptr.hpp; then
+			boost_include_path=$d/include
+			break
+		fi
+	done
+fi
+
+# Check the path with some specific headers.
+CPPFLAGS_SAVED="$CPPFLAGS"
+if test "${boost_include_path}" ; then
+	BOOST_INCLUDES="-I${boost_include_path}"
+	CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+fi
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
+  AC_MSG_ERROR([Missing required header files.]))
+
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly.  In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+# Boost offset_ptr is known to not compile on some platforms, depending on
+# boost version, its local configuration, and compiler.  Detect it.
+AC_MSG_CHECKING([Boost offset_ptr compiles])
+AC_TRY_COMPILE([
+#include <boost/interprocess/offset_ptr.hpp>
+],,
+[AC_MSG_RESULT(yes)
+ BOOST_OFFSET_PTR_WOULDFAIL=no],
+[AC_MSG_RESULT(no)
+ BOOST_OFFSET_PTR_WOULDFAIL=yes])
+
+# Detect build failure case known to happen with Boost installed via
+# FreeBSD ports
+if test "X$GXX" = "Xyes"; then
+   CXXFLAGS_SAVED="$CXXFLAGS"
+   CXXFLAGS="$CXXFLAGS -Werror"
+
+   AC_MSG_CHECKING([Boost numeric_cast compiles with -Werror])
+   AC_TRY_COMPILE([
+   #include <boost/numeric/conversion/cast.hpp>
+   ],[
+   return (boost::numeric_cast<short>(0));
+   ],[AC_MSG_RESULT(yes)
+      BOOST_NUMERIC_CAST_WOULDFAIL=no],
+   [AC_MSG_RESULT(no)
+    BOOST_NUMERIC_CAST_WOULDFAIL=yes])
+
+   CXXFLAGS="$CXXFLAGS_SAVED"
+else
+  # This doesn't matter for non-g++
+  BOOST_NUMERIC_CAST_WOULDFAIL=no
+fi
+
+AC_SUBST(BOOST_INCLUDES)
+
+CPPFLAGS="$CPPFLAGS_SAVED"
+AC_LANG_RESTORE
+])dnl AX_BOOST_FOR_BIND10
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index ac9ffb9..d93da51 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -141,7 +141,7 @@ The specific problem is printed in the log message.
 The thread for maintaining data source clients has received a command to
 reconfigure, and has now started this process.
 
-% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed succesfully
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed successfully
 The thread for maintaining data source clients has finished reconfiguring
 the data source clients, and is now running with the new configuration.
 
@@ -169,7 +169,7 @@ probably better to stop and restart it.
 
 % AUTH_DATA_SOURCE data source database file: %1
 This is a debug message produced by the authoritative server when it accesses a
-datebase data source, listing the file that is being accessed.
+database data source, listing the file that is being accessed.
 
 % AUTH_DNS_SERVICES_CREATED DNS services created
 This is a debug message indicating that the component that will handling
@@ -184,7 +184,7 @@ reason for the failure is given in the message.) The server will drop the
 packet.
 
 % AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the authoritiative server specified
+An error was encountered when the authoritative server specified
 statistics data which is invalid for the auth specification file.
 
 % AUTH_LOAD_TSIG loading TSIG keys
@@ -208,7 +208,7 @@ requests to b10-ddns) to handle it, but it failed.  The authoritative
 server returns SERVFAIL to the client on behalf of the separate
 process.  The error could be configuration mismatch between b10-auth
 and the recipient component, or it may be because the requests are
-coming too fast and the receipient process cannot keep up with the
+coming too fast and the recipient process cannot keep up with the
 rate, or some system level failure.  In either case this means the
 BIND 10 system is not working as expected, so the administrator should
 look into the cause and address the issue.  The log message includes
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index b34009d..88f80e2 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.xml
@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>June 20, 2012</date>
+    <date>December 18, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -100,7 +100,7 @@
       <varname>database_file</varname> defines the path to the
       SQLite3 zone file when using the sqlite datasource.
       The default is
-      <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+      <filename>/usr/local/var/bind10/zone.sqlite3</filename>.
     </para>
 
     <para>
@@ -157,6 +157,7 @@
       incoming TCP connections, in milliseconds. If the query
       is not sent within this time, the connection is closed.
       Setting this to 0 will disable TCP timeouts completely.
+      The default is 5000 (five seconds).
     </para>
 
 <!-- TODO: formating -->
@@ -165,6 +166,15 @@
     </para>
 
     <para>
+      <command>getstats</command> tells <command>b10-auth</command>
+      to report its defined statistics data in JSON format.
+      It will not report about unused counters.
+      This is used by the
+      <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry> daemon.
+      (The <command>sendstats</command> command is deprecated.)
+    </para>
+
+    <para>
       <command>loadzone</command> tells <command>b10-auth</command>
       to load or reload a zone file. The arguments include:
       <varname>class</varname> which optionally defines the class
@@ -181,13 +191,6 @@
     </para>
 
     <para>
-      <command>sendstats</command> tells <command>b10-auth</command>
-      to send its statistics data to
-      <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-      immediately.
-    </para>
-
-    <para>
       <command>shutdown</command> exits <command>b10-auth</command>.
       This has an optional <varname>pid</varname> argument to
       select the process ID to stop.
@@ -195,6 +198,28 @@
       if configured.)
     </para>
 
+    <para>
+      <command>start_ddns_forwarder</command> starts (or restarts) the
+      internal forwarding of DDNS Update messages.
+      This is used by the
+      <citerefentry><refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      daemon to tell <command>b10-auth</command> that DDNS Update
+      messages can be forwarded.
+      <note><simpara>This is not expected to be called by administrators;
+        it will be removed as a public command in the future.</simpara></note>
+    </para>
+
+    <para>
+      <command>stop_ddns_forwarder</command> stops the internal
+      forwarding of DDNS Update messages.
+      This is used by the
+      <citerefentry><refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      daemon to tell <command>b10-auth</command> that DDNS Update
+      messages should not be forwarded.
+      <note><simpara>This is not expected to be called by administrators;
+        it will be removed as a public command in the future.</simpara></note>
+    </para>
+
   </refsect1>
 
   <refsect1>
@@ -230,7 +255,7 @@
   <refsect1>
     <title>FILES</title>
     <para>
-      <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>
+      <filename>/usr/local/var/bind10/zone.sqlite3</filename>
       — Location for the SQLite3 zone database
       when <emphasis>database_file</emphasis> configuration is not
       defined.
@@ -244,6 +269,9 @@
         <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
+        <refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
diff --git a/src/bin/auth/tests/.gitignore b/src/bin/auth/tests/.gitignore
index d6d1ec8..a45eff7 100644
--- a/src/bin/auth/tests/.gitignore
+++ b/src/bin/auth/tests/.gitignore
@@ -1 +1,3 @@
 /run_unittests
+/example_base_inc.cc
+/example_nsec3_inc.cc
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 42f202b..36a3c68 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -7,7 +7,8 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
-AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_top_srcdir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DDSRC_DIR=\"$(abs_top_builddir)/src/lib/datasrc\"
 AM_CPPFLAGS += -DPLUGIN_DATA_PATH=\"$(abs_top_builddir)/src/bin/cfgmgr/plugins\"
@@ -50,7 +51,6 @@ run_unittests_SOURCES += config_syntax_unittest.cc
 run_unittests_SOURCES += command_unittest.cc
 run_unittests_SOURCES += common_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
-run_unittests_SOURCES += query_inmemory_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc
 run_unittests_SOURCES += datasrc_clients_builder_unittest.cc
@@ -81,6 +81,40 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 run_unittests_LDADD += $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 
+# The following are definitions for auto-generating test data for query
+# tests.
+BUILT_SOURCES = example_base_inc.cc example_nsec3_inc.cc
+BUILT_SOURCES += testdata/example-base.sqlite3
+BUILT_SOURCES += testdata/example-nsec3.sqlite3
+
+EXTRA_DIST = gen-query-testdata.py
+
+CLEANFILES += example_base_inc.cc example_nsec3_inc.cc
+
+example_base_inc.cc: $(srcdir)/testdata/example-base-inc.zone
+	$(PYTHON) $(srcdir)/gen-query-testdata.py \
+		$(srcdir)/testdata/example-base-inc.zone example_base_inc.cc
+
+example_nsec3_inc.cc: $(srcdir)/testdata/example-nsec3-inc.zone
+	$(PYTHON) $(srcdir)/gen-query-testdata.py \
+		$(srcdir)/testdata/example-nsec3-inc.zone example_nsec3_inc.cc
+
+testdata/example-base.sqlite3: testdata/example-base.zone
+	$(top_srcdir)/install-sh -c \
+		$(srcdir)/testdata/example-common-inc-template.zone \
+		testdata/example-common-inc.zone
+	$(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+		-c "{\"database_file\": \"$(builddir)/testdata/example-base.sqlite3\"}" \
+		example.com testdata/example-base.zone
+
+testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone
+	$(top_srcdir)/install-sh -c \
+		$(srcdir)/testdata/example-common-inc-template.zone \
+		testdata/example-common-inc.zone
+	$(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+		-c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
+		example.com testdata/example-nsec3.zone
+
 check-local:
 	B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
 
diff --git a/src/bin/auth/tests/gen-query-testdata.py b/src/bin/auth/tests/gen-query-testdata.py
new file mode 100755
index 0000000..a71deb0
--- /dev/null
+++ b/src/bin/auth/tests/gen-query-testdata.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""\
+This is a supplemental script to auto generate test data in the form of
+C++ source code from a DNS zone file.
+
+Usage: python gen-query-testdata.py source_file output-cc-file
+
+The usage doesn't matter much, though, because it's expected to be invoked
+from Makefile, and that would be only use case of this script.
+"""
+
+import sys
+import re
+
+# Markup for variable definition
+re_start_rr = re.compile('^;var=(.*)')
+
+# Skip lines starting with ';' (comments) or empty lines.  re_start_rr
+# will also match this expression, so it should be checked first.
+re_skip = re.compile('(^;)|(^\s*$)')
+
+def parse_input(input_file):
+    '''Build an internal list of RR data from the input source file.
+
+    It generates a list of (variable_name, list of RR) tuples, where
+    variable_name is the expected C++ variable name for the subsequent RRs
+    if they are expected to be named.  It can be an empty string if the RRs
+    are only expected to appear in the zone file.
+    The second element of the tuple is a list of strings, each of which
+    represents a single RR, e.g., "example.com 3600 IN A 192.0.2.1".
+
+    '''
+    result = []
+    rrs = None
+    with open(input_file) as f:
+        for line in f:
+            m = re_start_rr.match(line)
+            if m:
+                if rrs is not None:
+                    result.append((rr_varname, rrs))
+                rrs = []
+                rr_varname = m.group(1)
+            elif re_skip.match(line):
+                continue
+            else:
+                rrs.append(line.rstrip('\n'))
+
+        # if needed, store the last RRs (they are not followed by 'var=' mark)
+        if rrs is not None:
+            result.append((rr_varname, rrs))
+
+    return result
+
+def generate_variables(out_file, rrsets_data):
+    '''Generate a C++ source file containing a C-string variables for RRs.
+
+    This produces a definition of C-string for each RRset that is expected
+    to be named as follows:
+    const char* const var_name =
+        "example.com. 3600 IN A 192.0.2.1\n"
+        "example.com. 3600 IN A 192.0.2.2\n";
+
+    Escape character '\' in the string will be further escaped so it will
+    compile.
+
+    '''
+    with open(out_file, 'w') as out:
+        for (var_name, rrs) in rrsets_data:
+            if len(var_name) > 0:
+                out.write('const char* const ' + var_name + ' =\n')
+                # Combine all RRs, escaping '\' as a C-string
+                out.write('\n'.join(['    \"%s\\n\"' %
+                                     (rr.replace('\\', '\\\\'))
+                                     for rr in rrs]))
+                out.write(';\n')
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        sys.stderr.write('gen-query-testdata.py require 2 args\n')
+        sys.exit(1)
+    rrsets_data = parse_input(sys.argv[1])
+    generate_variables(sys.argv[2], rrsets_data)
+
diff --git a/src/bin/auth/tests/query_inmemory_unittest.cc b/src/bin/auth/tests/query_inmemory_unittest.cc
deleted file mode 100644
index f587ba2..0000000
--- a/src/bin/auth/tests/query_inmemory_unittest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <dns/name.h>
-#include <dns/message.h>
-#include <dns/rcode.h>
-#include <dns/opcode.h>
-
-#include <cc/data.h>
-
-#include <datasrc/client_list.h>
-
-#include <auth/query.h>
-
-#include <testutils/dnsmessage_test.h>
-
-#include <gtest/gtest.h>
-
-#include <string>
-
-using namespace isc::dns;
-using namespace isc::auth;
-using namespace isc::testutils;
-using isc::datasrc::ConfigurableClientList;
-using std::string;
-
-namespace {
-
-// The DNAME to do tests against
-const char* const dname_txt =
-    "dname.example.com. 3600 IN DNAME "
-    "somethinglong.dnametarget.example.com.\n";
-// This is not inside the zone, this is created at runtime
-const char* const synthetized_cname_txt =
-    "www.dname.example.com. 3600 IN CNAME "
-    "www.somethinglong.dnametarget.example.com.\n";
-
-// This is a subset of QueryTest using (subset of) the same test data, but
-// with the production in-memory data source.  Both tests should be eventually
-// unified to avoid duplicates.
-class InMemoryQueryTest : public ::testing::Test {
-protected:
-    InMemoryQueryTest() : list(RRClass::IN()), response(Message::RENDER) {
-        response.setRcode(Rcode::NOERROR());
-        response.setOpcode(Opcode::QUERY());
-        list.configure(isc::data::Element::fromJSON(
-                           "[{\"type\": \"MasterFiles\","
-                           "  \"cache-enable\": true, "
-                           "  \"params\": {\"example.com\": \"" +
-                           string(TEST_OWN_DATA_DIR "/example.zone") +
-                           "\"}}]"), true);
-    }
-
-    ConfigurableClientList list;
-    Message response;
-    Query query;
-};
-
-// A wrapper to check resulting response message commonly used in
-// tests below.
-// check_origin needs to be specified only when the authority section has
-// an SOA RR.  The interface is not generic enough but should be okay
-// for our test cases in practice.
-void
-responseCheck(Message& response, const isc::dns::Rcode& rcode,
-              unsigned int flags, const unsigned int ancount,
-              const unsigned int nscount, const unsigned int arcount,
-              const char* const expected_answer,
-              const char* const expected_authority,
-              const char* const expected_additional,
-              const Name& check_origin = Name::ROOT_NAME())
-{
-    // In our test cases QID, Opcode, and QDCOUNT should be constant, so
-    // we don't bother the test cases specifying these values.
-    headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
-                flags, 0, ancount, nscount, arcount);
-    if (expected_answer != NULL) {
-        rrsetsCheck(expected_answer,
-                    response.beginSection(Message::SECTION_ANSWER),
-                    response.endSection(Message::SECTION_ANSWER),
-                    check_origin);
-    }
-    if (expected_authority != NULL) {
-        rrsetsCheck(expected_authority,
-                    response.beginSection(Message::SECTION_AUTHORITY),
-                    response.endSection(Message::SECTION_AUTHORITY),
-                    check_origin);
-    }
-    if (expected_additional != NULL) {
-        rrsetsCheck(expected_additional,
-                    response.beginSection(Message::SECTION_ADDITIONAL),
-                    response.endSection(Message::SECTION_ADDITIONAL));
-    }
-}
-
-/*
- * Test a query under a domain with DNAME. We should get a synthetized CNAME
- * as well as the DNAME.
- *
- * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
- * as well. This includes tests pointing inside the zone, outside the zone,
- * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
- */
-TEST_F(InMemoryQueryTest, DNAME) {
-    query.process(list, Name("www.dname.example.com"), RRType::A(),
-                  response);
-
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
-        (string(dname_txt) + synthetized_cname_txt).c_str(),
-        NULL, NULL);
-}
-}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 84b7f8a..a22d2d7 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -12,10 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <map>
-#include <sstream>
-#include <vector>
-
 #include <boost/bind.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/static_assert.hpp>
@@ -24,9 +20,12 @@
 
 #include <dns/masterload.h>
 #include <dns/message.h>
+#include <dns/master_loader.h>
 #include <dns/name.h>
+#include <dns/nsec3hash.h>
 #include <dns/opcode.h>
 #include <dns/rcode.h>
+#include <dns/rrcollator.h>
 #include <dns/rrttl.h>
 #include <dns/rrtype.h>
 #include <dns/rdataclass.h>
@@ -40,6 +39,12 @@
 
 #include <gtest/gtest.h>
 
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <vector>
+
 using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
@@ -49,7 +54,7 @@ using namespace isc::testutils;
 
 namespace {
 
-// Simple wrapper for a sincle data source client.
+// Simple wrapper for a single data source client.
 // The list simply delegates all the answers to the single
 // client.
 class SingletonList : public ClientList {
@@ -79,150 +84,21 @@ private:
     DataSourceClient& client_;
 };
 
+// These are commonly used data (auto-generated).  There are some exceptional
+// data that are only used in a limited scenario, which are defined separately
+// below.
+#include <auth/tests/example_base_inc.cc>
+#include <auth/tests/example_nsec3_inc.cc>
 
-// This is the content of the mock zone (see below).
-// It's a sequence of textual RRs that is supposed to be parsed by
-// dns::masterLoad().  Some of the RRs are also used as the expected
-// data in specific tests, in which case they are referenced via specific
-// local variables (such as soa_txt).
-//
-// For readability consistency, all strings are placed in a separate line,
-// even if they are very short and can reasonably fit in a single line with
-// the corresponding variable.  For example, we write
-// const char* const foo_txt =
-//  "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-// instead of
-// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-const char* const soa_txt =
-    "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
-const char* const zone_ns_txt =
-    "example.com. 3600 IN NS glue.delegation.example.com.\n"
-    "example.com. 3600 IN NS noglue.example.com.\n"
-    "example.com. 3600 IN NS example.net.\n";
+// This is used only in one pathological test case.
 const char* const zone_ds_txt =
     "example.com. 3600 IN DS 57855 5 1 "
         "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const ns_addrs_txt =
-    "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
-    "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
-    "noglue.example.com. 3600 IN A 192.0.2.53\n";
-const char* const delegation_txt =
-    "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
-    "delegation.example.com. 3600 IN NS noglue.example.com.\n"
-    "delegation.example.com. 3600 IN NS cname.example.com.\n"
-    "delegation.example.com. 3600 IN NS example.org.\n";
-// Borrowed from the RFC4035
-const char* const delegation_ds_txt =
-    "delegation.example.com. 3600 IN DS 57855 5 1 "
-        "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const mx_txt =
-    "mx.example.com. 3600 IN MX 10 www.example.com.\n"
-    "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
-    "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
-const char* const www_a_txt =
-    "www.example.com. 3600 IN A 192.0.2.80\n";
-const char* const cname_txt =
-    "cname.example.com. 3600 IN CNAME www.example.com.\n";
-const char* const cname_nxdom_txt =
-    "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
-// CNAME Leading out of zone
-const char* const cname_out_txt =
-    "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
-// The DNAME to do tests against
-const char* const dname_txt =
-    "dname.example.com. 3600 IN DNAME "
-    "somethinglong.dnametarget.example.com.\n";
-// Some data at the dname node (allowed by RFC 2672)
-const char* const dname_a_txt =
-    "dname.example.com. 3600 IN A 192.0.2.5\n";
+
 // This is not inside the zone, this is created at runtime
 const char* const synthetized_cname_txt =
     "www.dname.example.com. 3600 IN CNAME "
     "www.somethinglong.dnametarget.example.com.\n";
-// The rest of data won't be referenced from the test cases.
-const char* const other_zone_rrs =
-    "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
-    "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
-    "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
-// Wildcards
-const char* const wild_txt =
-    "*.wild.example.com. 3600 IN A 192.0.2.7\n";
-const char* const nsec_wild_txt =
-    "*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
-const char* const cnamewild_txt =
-    "*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
-const char* const nsec_cnamewild_txt =
-    "*.cnamewild.example.com. 3600 IN NSEC "
-    "delegation.example.com. CNAME NSEC RRSIG\n";
-// Wildcard_nxrrset
-const char* const wild_txt_nxrrset =
-    "*.uwild.example.com. 3600 IN A 192.0.2.9\n";
-const char* const nsec_wild_txt_nxrrset =
-    "*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG\n";
-const char* const wild_txt_next =
-    "www.uwild.example.com. 3600 IN A 192.0.2.11\n";
-const char* const nsec_wild_txt_next =
-    "www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
-// Wildcard empty
-const char* const empty_txt =
-    "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
-const char* const nsec_empty_txt =
-    "b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
-const char* const empty_prev_txt =
-    "t.example.com. 3600 IN A 192.0.2.15\n";
-const char* const nsec_empty_prev_txt =
-    "t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
-// Used in NXDOMAIN proof test.  We are going to test some unusual case where
-// the best possible wildcard is below the "next domain" of the NSEC RR that
-// proves the NXDOMAIN, i.e.,
-// mx.example.com. (exist)
-// (.no.example.com. (qname, NXDOMAIN)
-// ).no.example.com. (exist)
-// *.no.example.com. (best possible wildcard, not exist)
-const char* const no_txt =
-    ").no.example.com. 3600 IN AAAA 2001:db8::53\n";
-// NSEC records.
-const char* const nsec_apex_txt =
-    "example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG\n";
-const char* const nsec_mx_txt =
-    "mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG\n";
-const char* const nsec_no_txt =
-    ").no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG\n";
-// We'll also test the case where a single NSEC proves both NXDOMAIN and the
-// non existence of wildcard.  The following records will be used for that
-// test.
-// ).no.example.com. (exist, whose NSEC proves everything)
-// *.no.example.com. (best possible wildcard, not exist)
-// nx.no.example.com. (NXDOMAIN)
-// nz.no.example.com. (exist)
-const char* const nz_txt =
-    "nz.no.example.com. 3600 IN AAAA 2001:db8::5300\n";
-const char* const nsec_nz_txt =
-    "nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG\n";
-const char* const nsec_nxdomain_txt =
-    "noglue.example.com. 3600 IN NSEC nonsec.example.com. A\n";
-
-// NSEC for the normal NXRRSET case
-const char* const nsec_www_txt =
-    "www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";
-
-// Authoritative data without NSEC
-const char* const nonsec_a_txt =
-    "nonsec.example.com. 3600 IN A 192.0.2.0\n";
-
-// NSEC3 RRs.  You may also need to add mapping to MockZoneFinder::hash_map_.
-const string nsec3_apex_txt =
-    "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 "
-    "aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG\n";
-const string nsec3_apex_rrsig_txt =
-    "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 "
-    "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
-const string nsec3_www_txt =
-    "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
-    "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-const string nsec3_www_rrsig_txt =
-    "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 "
-    "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
 
 // NSEC3 for wild.example.com (used in wildcard tests, will be added on
 // demand not to confuse other tests)
@@ -246,42 +122,13 @@ const char* const nsec3_uwild_txt =
     "t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 "
     "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
 
-// (Secure) delegation data; Delegation with DS record
-const char* const signed_delegation_txt =
-    "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const signed_delegation_ds_txt =
-    "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
-    "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";
-
 // (Secure) delegation data; Delegation without DS record (and both NSEC
 // and NSEC3 denying its existence)
-const char* const unsigned_delegation_txt =
-    "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_nsec_txt =
-    "unsigned-delegation.example.com. 3600 IN NSEC "
-    "unsigned-delegation-optout.example.com. NS RRSIG NSEC\n";
 // This one will be added on demand
 const char* const unsigned_delegation_nsec3_txt =
     "q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
     "aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
 
-// Delegation without DS record, and no direct matching NSEC3 record
-const char* const unsigned_delegation_optout_txt =
-    "unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_optout_nsec_txt =
-    "unsigned-delegation-optout.example.com. 3600 IN NSEC "
-    "*.uwild.example.com. NS RRSIG NSEC\n";
-
-// (Secure) delegation data; Delegation where the DS lookup will raise an
-// exception.
-const char* const bad_delegation_txt =
-    "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";
-
-// Delegation from an unsigned parent.  There's no DS, and there's no NSEC
-// or NSEC3 that proves it.
-const char* const nosec_delegation_txt =
-    "nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.\n";
-
 // A helper function that generates a textual representation of RRSIG RDATA
 // for the given covered type.  The resulting RRSIG may not necessarily make
 // sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -314,58 +161,14 @@ textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
     return (rrset);
 }
 
-// This is a mock Zone Finder class for testing.
-// It is a derived class of ZoneFinder for the convenient of tests.
-// Its find() method emulates the common behavior of protocol compliant
-// ZoneFinder classes, but simplifies some minor cases and also supports broken
-// behavior.
-// For simplicity, most names are assumed to be "in zone"; delegations
-// to child zones are identified by the existence of non origin NS records.
-// Another special name is "dname.example.com".  Query names under this name
-// will result in DNAME.
-// This mock zone doesn't handle empty non terminal nodes (if we need to test
-// such cases find() should have specialized code for it).
-class MockZoneFinder : public ZoneFinder {
+// Setup for faked NSEC3 hash used throughout this test.
+class TestNSEC3Hash : public NSEC3Hash {
+private:
+    typedef map<Name, string> NSEC3HashMap;
+    typedef NSEC3HashMap::value_type NSEC3HashPair;
+    NSEC3HashMap hash_map_;
 public:
-    MockZoneFinder() :
-        origin_(Name("example.com")),
-        bad_signed_delegation_name_("bad-delegation.example.com"),
-        dname_name_("dname.example.com"),
-        has_SOA_(true),
-        has_apex_NS_(true),
-        rrclass_(RRClass::IN()),
-        include_rrsig_anyway_(false),
-        use_nsec3_(false),
-        nsec_name_(origin_),
-        nsec3_fake_(NULL),
-        nsec3_name_(NULL)
-    {
-        stringstream zone_stream;
-        zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
-            delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
-            cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
-            dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
-            nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
-            nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
-            wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
-            wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
-            nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
-            empty_prev_txt << nsec_empty_prev_txt <<
-            nsec3_apex_txt << nsec3_www_txt <<
-            signed_delegation_txt << signed_delegation_ds_txt <<
-            unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
-            unsigned_delegation_optout_txt <<
-            unsigned_delegation_optout_nsec_txt <<
-            bad_delegation_txt << nosec_delegation_txt;
-
-        masterLoad(zone_stream, origin_, rrclass_,
-                   boost::bind(&MockZoneFinder::loadRRset, this, _1));
-
-        empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
-                                                    RRClass::IN(),
-                                                    RRType::NSEC(),
-                                                    RRTTL(3600)));
-
+    TestNSEC3Hash() {
         // (Faked) NSEC3 hash map.  For convenience we use hardcoded built-in
         // map instead of calculating and using actual hash.
         // The used hash values are borrowed from RFC5155 examples (they are
@@ -411,6 +214,79 @@ public:
         hash_map_[Name("www1.uwild.example.com")] =
             "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
     }
+    virtual string calculate(const Name& name) const {
+        const NSEC3HashMap::const_iterator found = hash_map_.find(name);
+        if (found != hash_map_.end()) {
+            return (found->second);
+        }
+        isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+                  << name);
+    }
+    virtual bool match(const rdata::generic::NSEC3PARAM&) const {
+        return (true);
+    }
+    virtual bool match(const rdata::generic::NSEC3&) const {
+        return (true);
+    }
+};
+
+class TestNSEC3HashCreator : public isc::dns::NSEC3HashCreator {
+public:
+    TestNSEC3HashCreator() {}
+    virtual isc::dns::NSEC3Hash*
+    create(const isc::dns::rdata::generic::NSEC3PARAM&) const {
+        return (new TestNSEC3Hash);
+    }
+
+    virtual isc::dns::NSEC3Hash*
+    create(const isc::dns::rdata::generic::NSEC3&) const {
+        return (new TestNSEC3Hash);
+    }
+
+    virtual isc::dns::NSEC3Hash*
+    create(uint8_t, uint16_t, const uint8_t*, size_t) const {
+        return (new TestNSEC3Hash);
+    }
+};
+
+// This is a mock Zone Finder class for testing.
+// It is a derived class of ZoneFinder for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// ZoneFinder classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; delegations
+// to child zones are identified by the existence of non origin NS records.
+// Another special name is "dname.example.com".  Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
+class MockZoneFinder : public ZoneFinder {
+public:
+    MockZoneFinder() :
+        origin_(Name("example.com")),
+        bad_signed_delegation_name_("bad-delegation.example.com"),
+        dname_name_("dname.example.com"),
+        has_SOA_(true),
+        has_apex_NS_(true),
+        rrclass_(RRClass::IN()),
+        include_rrsig_anyway_(false),
+        use_nsec3_(false),
+        nsec_name_(origin_),
+        nsec3_fake_(NULL),
+        nsec3_name_(NULL)
+    {
+        RRCollator collator(boost::bind(&MockZoneFinder::loadRRset, this, _1));
+        MasterLoader loader(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone",
+                            origin_, rrclass_,
+                            MasterLoaderCallbacks::getNullCallbacks(),
+                            collator.getCallback());
+        loader.load();
+
+        empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
+                                                    RRClass::IN(),
+                                                    RRType::NSEC(),
+                                                    RRTTL(3600)));
+    }
     virtual isc::dns::Name getOrigin() const { return (origin_); }
     virtual isc::dns::RRClass getClass() const { return (rrclass_); }
     virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
@@ -513,6 +389,19 @@ private:
     };
 
     void loadRRset(RRsetPtr rrset) {
+        // For simplicity we dynamically generate RRSIGs and add them below.
+        // The RRSIG RDATA should be consistent with that defined in the
+        // zone file.
+        if (rrset->getType() == RRType::RRSIG()) {
+            return;
+        }
+
+        // NSEC3PARAM is not used in the mock data source (and it would confuse
+        // non-NSEC3 test cases).
+        if (rrset->getType() == RRType::NSEC3PARAM()) {
+            return;
+        }
+
         if (rrset->getType() == RRType::NSEC3()) {
             // NSEC3 should go to the dedicated table
             nsec3_domains_[rrset->getName()][rrset->getType()] = rrset;
@@ -565,9 +454,7 @@ private:
     // Enabled when not NULL
     const FindNSEC3Result* nsec3_fake_;
     const Name* nsec3_name_;
-public:
-    // Public, to allow tests looking up the right names for something
-    map<Name, string> hash_map_;
+    TestNSEC3Hash nsec3_hash_;
 };
 
 // A helper function that generates a new RRset based on "wild_rrset",
@@ -627,11 +514,7 @@ MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
     // For brevity, we assume several things below: maps should have an
     // expected entry when operator[] is used; maps are not empty.
     for (int i = 0; i < labels; ++i) {
-        const string hlabel = hash_map_[name.split(i, labels - i)];
-        if (hlabel.empty()) {
-            isc_throw(isc::Unexpected, "findNSEC3() hash failure for " <<
-                      name.split(i, labels - i));
-        }
+        const string hlabel = nsec3_hash_.calculate(name.split(i, labels - i));
         const Name hname = Name(hlabel + ".example.com");
         // We don't use const_iterator so that we can use operator[] below
         Domains::iterator found_domain = nsec3_domains_.lower_bound(hname);
@@ -893,10 +776,55 @@ MockZoneFinder::find(const Name& name, const RRType& type,
     return (createContext(options,NXDOMAIN, RRsetPtr()));
 }
 
-class QueryTest : public ::testing::Test {
+enum DataSrcType {
+    MOCK,
+    INMEMORY,
+    SQLITE3
+};
+
+boost::shared_ptr<ClientList>
+createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
+    boost::shared_ptr<ConfigurableClientList> list;
+    switch (type) {
+    case MOCK:
+        return (boost::shared_ptr<ClientList>(new SingletonList(client)));
+    case INMEMORY:
+        list.reset(new ConfigurableClientList(RRClass::IN()));
+        list->configure(isc::data::Element::fromJSON(
+                            "[{\"type\": \"MasterFiles\","
+                            "  \"cache-enable\": true, "
+                            "  \"params\": {\"example.com\": \"" +
+                            string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+                            "\"}}]"), true);
+        return (list);
+    case SQLITE3:
+        // The copy should succeed; if it failed we should notice it in
+        // test cases.  However, we check the return value to avoid problems
+        // in some glibcs where "system()" is annotated with the "warn unused
+        // result" attribute.
+        EXPECT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+                                 "/example-base.sqlite3 "
+                                 TEST_OWN_DATA_BUILDDIR
+                                 "/example-base.sqlite3.copied"));
+        list.reset(new ConfigurableClientList(RRClass::IN()));
+        list->configure(isc::data::Element::fromJSON(
+                            "[{\"type\": \"sqlite3\","
+                            "  \"cache-enable\": false, "
+                            "  \"cache-zones\": [], "
+                            "  \"params\": {\"database_file\": \"" +
+                            string(TEST_OWN_DATA_BUILDDIR
+                                   "/example-base.sqlite3.copied") +
+                            "\"}}]"), true);
+         return (list);
+    default:
+        isc_throw(isc::Unexpected,
+                  "Unexpected data source type, should be a bug in test code");
+    }
+}
+
+class QueryTest : public ::testing::TestWithParam<DataSrcType> {
 protected:
     QueryTest() :
-        list(memory_client),
         qname(Name("www.example.com")), qclass(RRClass::IN()),
         qtype(RRType::A()), response(Message::RENDER),
         qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
@@ -906,22 +834,166 @@ protected:
                              "glue.delegation.example.com. 3600 IN RRSIG " +
                              getCommonRRSIGText("AAAA") + "\n" +
                              "noglue.example.com. 3600 IN RRSIG " +
-                             getCommonRRSIGText("A"))
+                             getCommonRRSIGText("A")),
+        base_zone_file(TEST_OWN_DATA_BUILDDIR "/example-base.zone"),
+        nsec3_zone_file(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone"),
+        common_zone_file(TEST_OWN_DATA_BUILDDIR "/example-common-inc.zone"),
+        rrsets_added_(false)
     {
+        // Set up the faked hash calculator.
+        setNSEC3HashCreator(&nsec3hash_creator_);
+
         response.setRcode(Rcode::NOERROR());
         response.setOpcode(Opcode::QUERY());
         // create and add a matching zone.
         mock_finder = new MockZoneFinder();
         memory_client.addZone(ZoneFinderPtr(mock_finder));
     }
+
+    virtual void SetUp() {
+        // clear the commonly included zone file.
+        ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+                                 "/example-common-inc-template.zone "
+                                 TEST_OWN_DATA_BUILDDIR
+                                 "/example-common-inc.zone"));
+
+        // We create data source clients here, not in the constructor, so this
+        // doesn't happen for derived test class.  This also ensures the
+        // data source clients are configured after setting NSEC3 hash in case
+        // there's dependency.
+        list_ = createDataSrcClientList(GetParam(), memory_client);
+    }
+
+    virtual void TearDown() {
+        // make sure to clear the commonly included zone file to prevent
+        // any remaining contents from affecting the next test.
+        ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+                                 "/example-common-inc-template.zone "
+                                 TEST_OWN_DATA_BUILDDIR
+                                 "/example-common-inc.zone"));
+    }
+
+    virtual ~QueryTest() {
+        // Make sure we reset the hash creator to the default
+        setNSEC3HashCreator(NULL);
+    }
+
+    void enableNSEC3(const vector<string>& rrsets_to_add) {
+        boost::shared_ptr<ConfigurableClientList> new_list;
+        switch (GetParam()) {
+        case MOCK:
+            mock_finder->setNSEC3Flag(true);
+            addRRsets(rrsets_to_add, *list_, "");
+            break;
+        case INMEMORY:
+            addRRsets(rrsets_to_add, *list_, nsec3_zone_file);
+            break;
+        case SQLITE3:
+            ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+                                     "/example-nsec3.sqlite3 "
+                                     TEST_OWN_DATA_BUILDDIR
+                                     "/example-nsec3.sqlite3.copied"));
+            new_list.reset(new ConfigurableClientList(RRClass::IN()));
+            new_list->configure(isc::data::Element::fromJSON(
+                                    "[{\"type\": \"sqlite3\","
+                                    "  \"cache-enable\": false, "
+                                    "  \"cache-zones\": [], "
+                                    "  \"params\": {\"database_file\": \"" +
+                                    string(TEST_OWN_DATA_BUILDDIR
+                                           "/example-nsec3.sqlite3.copied") +
+                                    "\"}}]"), true);
+            addRRsets(rrsets_to_add, *new_list, "");
+            list_ = new_list;
+            break;
+        }
+    }
+
+    // A helper to add some RRsets to the test zone in the middle of a test
+    // case.  The detailed behavior is quite different depending on the
+    // data source type, and not all parameters are used in all cases.
+    //
+    // Note: due to limitation of its implementation, this method doesn't
+    // work correctly for in-memory if called more than once.  This condition
+    // is explicitly checked so any accidental violation would be noted as a
+    // test failure.
+    void addRRsets(const vector<string>& rrsets_to_add, ClientList& list,
+                   const string& zone_file)
+    {
+        boost::shared_ptr<ConfigurableClientList> new_list;
+        ofstream ofs;
+
+        switch (GetParam()) {
+        case MOCK:
+            // directly add them to the mock data source; ignore the passed
+            // list.
+            for (vector<string>::const_iterator it = rrsets_to_add.begin();
+                 it != rrsets_to_add.end();
+                 ++it) {
+                mock_finder->addRecord(*it);
+            }
+            break;
+        case INMEMORY:
+            ASSERT_FALSE(rrsets_added_);
+            rrsets_added_ = true;
+
+            // dump the RRsets to be added to the placeholder of commonly
+            // included zone file (removing any existing contents) and do
+            // full reconfiguration.
+            ofs.open(common_zone_file.c_str(), ios_base::trunc);
+            for (vector<string>::const_iterator it = rrsets_to_add.begin();
+                 it != rrsets_to_add.end();
+                 ++it) {
+                ofs << *it << "\n";
+                ofs << createRRSIG(textToRRset(*it))->toText() << "\n";
+            }
+            ofs.close();
+
+            new_list.reset(new ConfigurableClientList(RRClass::IN()));
+            new_list->configure(isc::data::Element::fromJSON(
+                                    "[{\"type\": \"MasterFiles\","
+                                    "  \"cache-enable\": true, "
+                                    "  \"params\": {\"example.com\": \"" +
+                                    zone_file + "\"}}]"), true);
+            list_ = new_list;
+            break;
+        case SQLITE3:
+            const Name origin("example.com");
+            ZoneUpdaterPtr updater =
+                list.find(origin, true, false).dsrc_client_->
+                getUpdater(origin, false);
+            for (vector<string>::const_iterator it = rrsets_to_add.begin();
+                 it != rrsets_to_add.end();
+                 ++it) {
+                ConstRRsetPtr rrset = textToRRset(*it);
+                updater->addRRset(*rrset);
+                updater->addRRset(*createRRSIG(rrset));
+            }
+            updater->commit();
+            break;
+        }
+    }
+
+private:
+    // A helper for enableNSEC3, creating an RRSIG RRset for the corresponding
+    // non-sig RRset, using the commonly used parameters.
+    static ConstRRsetPtr createRRSIG(ConstRRsetPtr rrset) {
+        RRsetPtr sig_rrset(new RRset(rrset->getName(), rrset->getClass(),
+                                     RRType::RRSIG(), rrset->getTTL()));
+        sig_rrset->addRdata(generic::RRSIG(
+                                getCommonRRSIGText(rrset->getType().
+                                                   toText())));
+        return (sig_rrset);
+    }
+
+protected:
     MockZoneFinder* mock_finder;
     // We use InMemoryClient here. We could have some kind of mock client
     // here, but historically, the Query supported only InMemoryClient
     // (originally named MemoryDataSrc) and was tested with it, so we keep
     // it like this for now.
     InMemoryClient memory_client;
-    // A wrapper client list to wrap the single data source.
-    SingletonList list;
+
+    boost::shared_ptr<ClientList> list_;
     const Name qname;
     const RRClass qclass;
     const RRType qtype;
@@ -930,6 +1002,37 @@ protected:
     const uint16_t query_code;
     const string ns_addrs_and_sig_txt; // convenient shortcut
     Query query;
+    TestNSEC3Hash nsec3_hash_;
+    vector<string> rrsets_to_add_;
+    const string base_zone_file;
+private:
+    const string nsec3_zone_file;
+    const string common_zone_file;
+    const TestNSEC3HashCreator nsec3hash_creator_;
+    bool rrsets_added_;
+};
+
+// We test the in-memory and SQLite3 data source implementations.  SQLite3
+// will require a loadable module, which doesn't work with static link for
+// all platforms.
+INSTANTIATE_TEST_CASE_P(, QueryTest,
+                        ::testing::Values(MOCK, INMEMORY
+#ifndef USE_STATIC_LINK
+                                          , SQLITE3
+#endif
+                            ));
+
+// This inherit the QueryTest cases except for the parameterized setup;
+// it's intended to be used selected test cases that only work for mock
+// data sources either because of some limitation or because of the type of
+// tests (relying on a "broken" data source behavior that can't happen with
+// production data source implementations).
+class QueryTestForMockOnly : public QueryTest {
+protected:
+    // Override SetUp() to avoid parameterized setup
+    virtual void SetUp() {
+        list_ = createDataSrcClientList(MOCK, memory_client);
+    }
 };
 
 // A wrapper to check resulting response message commonly used in
@@ -969,7 +1072,7 @@ responseCheck(Message& response, const isc::dns::Rcode& rcode,
     }
 }
 
-TEST_F(QueryTest, noZone) {
+TEST_P(QueryTest, noZone) {
     // There's no zone in the memory datasource.  So the response should have
     // REFUSED.
     InMemoryClient empty_memory_client;
@@ -979,15 +1082,15 @@ TEST_F(QueryTest, noZone) {
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
-TEST_F(QueryTest, exactMatch) {
-    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatch) {
+    EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, exactMatchMultipleQueries) {
-    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatchMultipleQueries) {
+    EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -996,27 +1099,26 @@ TEST_F(QueryTest, exactMatchMultipleQueries) {
     response.clear(isc::dns::Message::RENDER);
     response.setRcode(Rcode::NOERROR());
     response.setOpcode(Opcode::QUERY());
-    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+    EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
     // find match rrset
     SCOPED_TRACE("Second query");
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, exactMatchIgnoreSIG) {
+TEST_P(QueryTest, exactMatchIgnoreSIG) {
     // Check that we do not include the RRSIG when not requested even when
     // we receive it from the data source.
     mock_finder->setIncludeRRSIGAnyway(true);
-    EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+    EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, dnssecPositive) {
+TEST_P(QueryTest, dnssecPositive) {
     // Just like exactMatch, but the signatures should be included as well
-    EXPECT_NO_THROW(query.process(list, qname, qtype, response,
-                                  true));
+    EXPECT_NO_THROW(query.process(*list_, qname, qtype, response, true));
     // find match rrset
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
                   (www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
@@ -1031,10 +1133,10 @@ TEST_F(QueryTest, dnssecPositive) {
                   ns_addrs_and_sig_txt.c_str());
 }
 
-TEST_F(QueryTest, exactAddrMatch) {
+TEST_P(QueryTest, exactAddrMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    EXPECT_NO_THROW(query.process(list,
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("noglue.example.com"),
                                   qtype, response));
 
@@ -1044,10 +1146,10 @@ TEST_F(QueryTest, exactAddrMatch) {
                   "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
 }
 
-TEST_F(QueryTest, apexNSMatch) {
+TEST_P(QueryTest, apexNSMatch) {
     // find match rrset, omit authority data which has already been provided
     // in the answer section from the authority section.
-    EXPECT_NO_THROW(query.process(list, Name("example.com"),
+    EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
                                   RRType::NS(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
@@ -1055,10 +1157,16 @@ TEST_F(QueryTest, apexNSMatch) {
 }
 
 // test type any query logic
-TEST_F(QueryTest, exactAnyMatch) {
+TEST_P(QueryTest, exactAnyMatch) {
+    // This is an in-memory specific bug (#2585), until it's fixed we
+    // tentatively skip the test for in-memory
+    if (GetParam() == INMEMORY) {
+        return;
+    }
+
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    EXPECT_NO_THROW(query.process(list, Name("noglue.example.com"),
+    EXPECT_NO_THROW(query.process(*list_, Name("noglue.example.com"),
                                   RRType::ANY(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
@@ -1069,10 +1177,10 @@ TEST_F(QueryTest, exactAnyMatch) {
                   "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
 }
 
-TEST_F(QueryTest, apexAnyMatch) {
+TEST_P(QueryTest, apexAnyMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
-    EXPECT_NO_THROW(query.process(list, Name("example.com"),
+    EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
                   (string(soa_txt) + string(zone_ns_txt) +
@@ -1080,23 +1188,23 @@ TEST_F(QueryTest, apexAnyMatch) {
                   NULL, ns_addrs_txt, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, mxANYMatch) {
-    EXPECT_NO_THROW(query.process(list, Name("mx.example.com"),
+TEST_P(QueryTest, mxANYMatch) {
+    EXPECT_NO_THROW(query.process(*list_, Name("mx.example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
                   (string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
                   (string(ns_addrs_txt) + string(www_a_txt)).c_str());
 }
 
-TEST_F(QueryTest, glueANYMatch) {
-    EXPECT_NO_THROW(query.process(list, Name("delegation.example.com"),
+TEST_P(QueryTest, glueANYMatch) {
+    EXPECT_NO_THROW(query.process(*list_, Name("delegation.example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                   NULL, delegation_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, nodomainANY) {
-    EXPECT_NO_THROW(query.process(list, Name("nxdomain.example.com"),
+TEST_P(QueryTest, nodomainANY) {
+    EXPECT_NO_THROW(query.process(*list_, Name("nxdomain.example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
                   NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -1105,17 +1213,20 @@ TEST_F(QueryTest, nodomainANY) {
 // This tests that when we need to look up Zone's apex NS records for
 // authoritative answer, and there is no apex NS records. It should
 // throw in that case.
-TEST_F(QueryTest, noApexNS) {
+//
+// This only works with mock data source (for production datasrc the
+// post-load would reject such a zone)
+TEST_F(QueryTestForMockOnly, noApexNS) {
     // Disable apex NS record
     mock_finder->setApexNSFlag(false);
 
-    EXPECT_THROW(query.process(list, Name("noglue.example.com"), qtype,
+    EXPECT_THROW(query.process(*list_, Name("noglue.example.com"), qtype,
                                response), Query::NoApexNS);
     // We don't look into the response, as it threw
 }
 
-TEST_F(QueryTest, delegation) {
-    EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, delegation) {
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("delegation.example.com"),
                                   qtype, response));
 
@@ -1123,19 +1234,19 @@ TEST_F(QueryTest, delegation) {
                   NULL, delegation_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, delegationWithDNSSEC) {
+TEST_P(QueryTest, delegationWithDNSSEC) {
     // Similar to the previous one, but with requesting DNSSEC.
     // In this case the parent zone would behave as unsigned, so the result
     // should be just like non DNSSEC delegation.
-    query.process(list, Name("www.nosec-delegation.example.com"),
+    query.process(*list_, Name("www.nosec-delegation.example.com"),
                   qtype, response, true);
 
     responseCheck(response, Rcode::NOERROR(), 0, 0, 1, 0,
                   NULL, nosec_delegation_txt, NULL);
 }
 
-TEST_F(QueryTest, secureDelegation) {
-    EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureDelegation) {
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("foo.signed-delegation.example.com"),
                                   qtype, response, true));
 
@@ -1149,8 +1260,8 @@ TEST_F(QueryTest, secureDelegation) {
                   NULL);
 }
 
-TEST_F(QueryTest, secureUnsignedDelegation) {
-    EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureUnsignedDelegation) {
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("foo.unsigned-delegation.example.com"),
                                   qtype, response, true));
 
@@ -1164,33 +1275,33 @@ TEST_F(QueryTest, secureUnsignedDelegation) {
                   NULL);
 }
 
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3) {
     // Similar to the previous case, but the zone is signed with NSEC3,
     // and this delegation is NOT an optout.
-    const Name insecurechild_name("unsigned-delegation.example.com");
-    mock_finder->setNSEC3Flag(true);
-    mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+    rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+    enableNSEC3(rrsets_to_add_);
 
-    query.process(list,
+    query.process(*list_,
                   Name("foo.unsigned-delegation.example.com"),
                   qtype, response, true);
 
     // The response should contain the NS and matching NSEC3 with its RRSIG
+    const Name insecurechild_name("unsigned-delegation.example.com");
     responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
                   NULL,
                   (string(unsigned_delegation_txt) +
                    string(unsigned_delegation_nsec3_txt) +
-                   mock_finder->hash_map_[insecurechild_name] +
+                   nsec3_hash_.calculate(insecurechild_name) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3")).c_str(),
                   NULL);
 }
 
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
     // Similar to the previous case, but the delegation is an optout.
-    mock_finder->setNSEC3Flag(true);
+    enableNSEC3(rrsets_to_add_);
 
-    query.process(list,
+    query.process(*list_,
                   Name("foo.unsigned-delegation.example.com"),
                   qtype, response, true);
 
@@ -1202,44 +1313,46 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
                   NULL,
                   (string(unsigned_delegation_txt) +
                    string(nsec3_apex_txt) +
-                   mock_finder->hash_map_[mock_finder->getOrigin()] +
+                   nsec3_hash_.calculate(mock_finder->getOrigin()) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n" +
                    string(nsec3_www_txt) +
-                   mock_finder->hash_map_[Name("www.example.com")] +
+                   nsec3_hash_.calculate(Name("www.example.com")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3")).c_str(),
                   NULL);
 }
 
-TEST_F(QueryTest, badSecureDelegation) {
+TEST_F(QueryTestForMockOnly, badSecureDelegation) {
+    // This is a broken data source scenario; works only with mock.
+
     // Test whether exception is raised if DS query at delegation results in
     // something different than SUCCESS or NXRRSET
-    EXPECT_THROW(query.process(list,
+    EXPECT_THROW(query.process(*list_,
                                Name("bad-delegation.example.com"),
                                qtype, response, true), Query::BadDS);
 
     // But only if DNSSEC is requested (it shouldn't even try to look for
     // the DS otherwise)
-    EXPECT_NO_THROW(query.process(list,
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("bad-delegation.example.com"),
                                   qtype, response));
 }
 
 
-TEST_F(QueryTest, nxdomain) {
-    EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, nxdomain) {
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("nxdomain.example.com"), qtype,
                                   response));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
                   NULL, soa_txt, NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxdomainWithNSEC) {
+TEST_P(QueryTest, nxdomainWithNSEC) {
     // NXDOMAIN with DNSSEC proof.  We should have SOA, NSEC that proves
     // NXDOMAIN and NSEC that proves nonexistence of matching wildcard,
     // as well as their RRSIGs.
-    EXPECT_NO_THROW(query.process(list,
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("nxdomain.example.com"), qtype,
                                   response, true));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
@@ -1255,12 +1368,18 @@ TEST_F(QueryTest, nxdomainWithNSEC) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxdomainWithNSEC2) {
+TEST_P(QueryTest, nxdomainWithNSEC2) {
+    // there seems to be a bug in the SQLite3 (or database in general) data
+    // source and this doesn't work (Trac #2586).
+    if (GetParam() == SQLITE3) {
+        return;
+    }
+
     // See comments about no_txt.  In this case the best possible wildcard
     // is derived from the next domain of the NSEC that proves NXDOMAIN, and
     // the NSEC to provide the non existence of wildcard is different from
     // the first NSEC.
-    query.process(list, Name("(.no.example.com"), qtype, response,
+    query.process(*list_, Name("(.no.example.com"), qtype, response,
                   true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
                   NULL, (string(soa_txt) +
@@ -1275,10 +1394,17 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
+TEST_P(QueryTest, nxdomainWithNSECDuplicate) {
+    // there seems to be a bug in the SQLite3 (or database in general) data
+    // source and this doesn't work.  This is probably the same type of bug
+    // as nxdomainWithNSEC2 (Trac #2586).
+    if (GetParam() == SQLITE3) {
+        return;
+    }
+
     // See comments about nz_txt.  In this case we only need one NSEC,
     // which proves both NXDOMAIN and the non existence of wildcard.
-    query.process(list, Name("nx.no.example.com"), qtype, response,
+    query.process(*list_, Name("nx.no.example.com"), qtype, response,
                   true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
                   NULL, (string(soa_txt) +
@@ -1290,52 +1416,62 @@ TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxdomainBadNSEC1) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC1) {
+    // This is a broken data source scenario; works only with mock.
+
     // ZoneFinder::find() returns NXDOMAIN with non NSEC RR.
     mock_finder->setNSECResult(Name("badnsec.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->dname_rrset_);
-    EXPECT_THROW(query.process(list, Name("badnsec.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("badnsec.example.com"),
                                qtype, response, true),
                  std::bad_cast);
 }
 
-TEST_F(QueryTest, nxdomainBadNSEC2) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC2) {
+    // This is a broken data source scenario; works only with mock.
+
     // ZoneFinder::find() returns NXDOMAIN with an empty NSEC RR.
     mock_finder->setNSECResult(Name("emptynsec.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->empty_nsec_rrset_);
-    EXPECT_THROW(query.process(list, Name("emptynsec.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("emptynsec.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
 
-TEST_F(QueryTest, nxdomainBadNSEC3) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC) {
+    // This is a broken data source scenario; works only with mock.
+
     // "no-wildcard proof" returns SUCCESS.  it should be NXDOMAIN.
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::SUCCESS,
                                mock_finder->dname_rrset_);
-    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
 
-TEST_F(QueryTest, nxdomainBadNSEC4) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC4) {
+    // This is a broken data source scenario; works only with mock.
+
     // "no-wildcard proof" doesn't return RRset.
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::NXDOMAIN, ConstRRsetPtr());
-    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
 
-TEST_F(QueryTest, nxdomainBadNSEC5) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC5) {
+    // This is a broken data source scenario; works only with mock.
+
     // "no-wildcard proof" returns non NSEC.
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->dname_rrset_);
     // This is a bit odd, but we'll simply include the returned RRset.
-    query.process(list, Name("nxdomain.example.com"), qtype,
+    query.process(*list_, Name("nxdomain.example.com"), qtype,
                   response, true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
                   NULL, (string(soa_txt) +
@@ -1350,28 +1486,30 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxdomainBadNSEC6) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC6) {
+    // This is a broken data source scenario; works only with mock.
+
     // "no-wildcard proof" returns empty NSEC.
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->empty_nsec_rrset_);
-    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
                                qtype, response, true),
                  Query::BadNSEC);
 }
 
-TEST_F(QueryTest, nxrrset) {
-    EXPECT_NO_THROW(query.process(list, Name("www.example.com"),
+TEST_P(QueryTest, nxrrset) {
+    EXPECT_NO_THROW(query.process(*list_, Name("www.example.com"),
                                   RRType::TXT(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
                   NULL, soa_txt, NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxrrsetWithNSEC) {
+TEST_P(QueryTest, nxrrsetWithNSEC) {
     // NXRRSET with DNSSEC proof.  We should have SOA, NSEC that proves the
     // NXRRSET and their RRSIGs.
-    query.process(list, Name("www.example.com"), RRType::TXT(),
+    query.process(*list_, Name("www.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1383,7 +1521,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, emptyNameWithNSEC) {
+TEST_P(QueryTest, emptyNameWithNSEC) {
     // Empty non terminal with DNSSEC proof.  This is one of the cases of
     // Section 3.1.3.2 of RFC4035.
     // mx.example.com. NSEC ).no.example.com. proves no.example.com. is a
@@ -1392,7 +1530,7 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
     // exact match), so we only need one NSEC.
     // From the point of the Query::process(), this is actually no different
     // from the other NXRRSET case, but we check that explicitly just in case.
-    query.process(list, Name("no.example.com"), RRType::A(),
+    query.process(*list_, Name("no.example.com"), RRType::A(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1404,11 +1542,11 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxrrsetWithoutNSEC) {
+TEST_P(QueryTest, nxrrsetWithoutNSEC) {
     // NXRRSET with DNSSEC proof requested, but there's no NSEC at that node.
     // This is an unexpected event (if the zone is supposed to be properly
     // signed with NSECs), but we accept and ignore the oddity.
-    query.process(list, Name("nonsec.example.com"), RRType::TXT(),
+    query.process(*list_, Name("nonsec.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -1417,10 +1555,10 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, wildcardNSEC) {
+TEST_P(QueryTest, wildcardNSEC) {
     // The qname matches *.wild.example.com.  The response should contain
     // an NSEC that proves the non existence of a closer name.
-    query.process(list, Name("www.wild.example.com"), RRType::A(),
+    query.process(*list_, Name("www.wild.example.com"), RRType::A(),
                   response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
                   (string(wild_txt).replace(0, 1, "www") +
@@ -1437,10 +1575,10 @@ TEST_F(QueryTest, wildcardNSEC) {
                   mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, CNAMEwildNSEC) {
+TEST_P(QueryTest, CNAMEwildNSEC) {
     // Similar to the previous case, but the matching wildcard record is
     // CNAME.
-    query.process(list, Name("www.cnamewild.example.com"),
+    query.process(*list_, Name("www.cnamewild.example.com"),
                   RRType::A(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
                   (string(cnamewild_txt).replace(0, 1, "www") +
@@ -1453,17 +1591,17 @@ TEST_F(QueryTest, CNAMEwildNSEC) {
                   mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, wildcardNSEC3) {
+TEST_P(QueryTest, wildcardNSEC3) {
     // Similar to wildcardNSEC, but the zone is signed with NSEC3.
     // The next closer is y.wild.example.com, the covering NSEC3 for it
     // is (in our setup) the NSEC3 for the apex.
-    mock_finder->setNSEC3Flag(true);
-
-    // This is NSEC3 for wild.example.com, which will be used in the middle
+    //
+    // Adding NSEC3 for wild.example.com, which will be used in the middle
     // of identifying the next closer name.
-    mock_finder->addRecord(nsec3_atwild_txt);
+    rrsets_to_add_.push_back(nsec3_atwild_txt);
+    enableNSEC3(rrsets_to_add_);
 
-    query.process(list, Name("x.y.wild.example.com"), RRType::A(),
+    query.process(*list_, Name("x.y.wild.example.com"), RRType::A(),
                   response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
                   (string(wild_txt).replace(0, 1, "x.y") +
@@ -1474,35 +1612,37 @@ TEST_F(QueryTest, wildcardNSEC3) {
                    getCommonRRSIGText("NS") + "\n" +
                    // NSEC3 for the wildcard proof and its RRSIG
                    string(nsec3_apex_txt) +
-                   mock_finder->hash_map_[Name("example.com.")] +
+                   nsec3_hash_.calculate(Name("example.com.")) +
                    string(".example.com. 3600 IN RRSIG ") +
                    getCommonRRSIGText("NSEC3")).c_str(),
                   NULL, // we are not interested in additionals in this test
                   mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, CNAMEwildNSEC3) {
+TEST_P(QueryTest, CNAMEwildNSEC3) {
     // Similar to CNAMEwildNSEC, but with NSEC3.
     // The next closer is qname itself, the covering NSEC3 for it
     // is (in our setup) the NSEC3 for the www.example.com.
-    mock_finder->setNSEC3Flag(true);
-    mock_finder->addRecord(nsec3_atcnamewild_txt);
+    rrsets_to_add_.push_back(nsec3_atcnamewild_txt);
+    enableNSEC3(rrsets_to_add_);
 
-    query.process(list, Name("www.cnamewild.example.com"),
+    query.process(*list_, Name("www.cnamewild.example.com"),
                   RRType::A(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
                   (string(cnamewild_txt).replace(0, 1, "www") +
                    string("www.cnamewild.example.com. 3600 IN RRSIG ") +
                    getCommonRRSIGText("CNAME") + "\n").c_str(),
                   (string(nsec3_www_txt) +
-                   mock_finder->hash_map_[Name("www.example.com.")] +
+                   nsec3_hash_.calculate(Name("www.example.com.")) +
                    string(".example.com. 3600 IN RRSIG ") +
                    getCommonRRSIGText("NSEC3")).c_str(),
                   NULL, // we are not interested in additionals in this test
                   mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, badWildcardNSEC3) {
+TEST_F(QueryTestForMockOnly, badWildcardNSEC3) {
+    // This is a broken data source scenario; works only with mock.
+
     // Similar to wildcardNSEC3, but emulating run time collision by
     // returning NULL in the next closer proof for the closest encloser
     // proof.
@@ -1511,46 +1651,58 @@ TEST_F(QueryTest, badWildcardNSEC3) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC3);
 }
 
-TEST_F(QueryTest, badWildcardProof1) {
+TEST_F(QueryTestForMockOnly, badWildcardProof1) {
+    // This is a broken data source scenario; works only with mock.
+
     // Unexpected case in wildcard proof: ZoneFinder::find() returns SUCCESS
     // when NXDOMAIN is expected.
     mock_finder->setNSECResult(Name("www.wild.example.com"),
                                ZoneFinder::SUCCESS,
                                mock_finder->dname_rrset_);
-    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC);
 }
 
-TEST_F(QueryTest, badWildcardProof2) {
+TEST_F(QueryTestForMockOnly, badWildcardProof2) {
+    // This is a broken data source scenario; works only with mock.
+
     // "wildcard proof" doesn't return RRset.
     mock_finder->setNSECResult(Name("www.wild.example.com"),
                                ZoneFinder::NXDOMAIN, ConstRRsetPtr());
-    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC);
 }
 
-TEST_F(QueryTest, badWildcardProof3) {
+TEST_F(QueryTestForMockOnly, badWildcardProof3) {
+    // This is a broken data source scenario; works only with mock.
+
     // "wildcard proof" returns empty NSEC.
     mock_finder->setNSECResult(Name("www.wild.example.com"),
                                ZoneFinder::NXDOMAIN,
                                mock_finder->empty_nsec_rrset_);
-    EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
                                RRType::A(), response, true),
                  Query::BadNSEC);
 }
 
-TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+    // This is an in-memory specific bug (#2585), until it's fixed we
+    // tentatively skip the test for in-memory
+    if (GetParam() == INMEMORY) {
+        return;
+    }
+
     // NXRRSET on WILDCARD with DNSSEC proof.  We should have SOA, NSEC that
     // proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
     // which proves both NXDOMAIN and the non existence RRSETs of wildcard.
-    query.process(list, Name("www.wild.example.com"), RRType::TXT(),
+    query.process(*list_, Name("www.wild.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1562,12 +1714,18 @@ TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC) {
+    // This is an in-memory specific bug (#2585), until it's fixed we
+    // tentatively skip the test for in-memory
+    if (GetParam() == INMEMORY) {
+        return;
+    }
+
     // WILDCARD + NXRRSET with DNSSEC proof.  We should have SOA, NSEC that
     // proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
     // one proves NXDOMAIN and the other proves non existence RRSETs of
     // wildcard.
-    query.process(list, Name("www1.uwild.example.com"),
+    query.process(*list_, Name("www1.uwild.example.com"),
                   RRType::TXT(), response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1582,15 +1740,15 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC3) {
     // Similar to the previous case, but providing NSEC3 proofs according to
     // RFC5155 Section 7.2.5.
 
-    mock_finder->addRecord(nsec3_wild_txt);
-    mock_finder->addRecord(nsec3_uwild_txt);
-    mock_finder->setNSEC3Flag(true);
+    rrsets_to_add_.push_back(nsec3_wild_txt);
+    rrsets_to_add_.push_back(nsec3_uwild_txt);
+    enableNSEC3(rrsets_to_add_);
 
-    query.process(list, Name("www1.uwild.example.com"),
+    query.process(*list_, Name("www1.uwild.example.com"),
                   RRType::TXT(), response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
@@ -1599,23 +1757,25 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
                    getCommonRRSIGText("SOA") + "\n" +
                    // NSEC3 for the closest encloser + its RRSIG
                    string(nsec3_uwild_txt) +
-                   mock_finder->hash_map_[Name("uwild.example.com.")] +
+                   nsec3_hash_.calculate(Name("uwild.example.com.")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n" +
                    // NSEC3 for the next closer + its RRSIG
                    string(nsec3_www_txt) +
-                   mock_finder->hash_map_[Name("www.example.com.")] +
+                   nsec3_hash_.calculate(Name("www.example.com.")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n" +
                    // NSEC3 for the wildcard + its RRSIG
                    string(nsec3_wild_txt) +
-                   mock_finder->hash_map_[Name("*.uwild.example.com.")] +
+                   nsec3_hash_.calculate(Name("*.uwild.example.com.")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3")).c_str(),
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Collision) {
+    // This is a broken data source scenario; works only with mock.
+
     // Similar to the previous case, but emulating run time collision by
     // returning NULL in the next closer proof for the closest encloser
     // proof.
@@ -1624,15 +1784,17 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
 
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Broken) {
+    // This is a broken data source scenario; works only with mock.
+
     // Similar to wildcardNxrrsetWithNSEC3, but no matching NSEC3 for the
     // wildcard name will be returned.  This shouldn't happen in a reasonably
-    // NSEC-signed zone, and should result in an exception.
+    // NSEC3-signed zone, and should result in an exception.
     mock_finder->setNSEC3Flag(true);
     const Name wname("*.uwild.example.com.");
     ZoneFinder::FindNSEC3Result nsec3(false, 0, textToRRset(nsec3_apex_txt),
@@ -1641,16 +1803,16 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
     mock_finder->addRecord(nsec3_wild_txt);
     mock_finder->addRecord(nsec3_uwild_txt);
 
-    EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
 
-TEST_F(QueryTest, wildcardEmptyWithNSEC) {
+TEST_P(QueryTest, wildcardEmptyWithNSEC) {
     // Empty WILDCARD with DNSSEC proof.  We should have SOA, NSEC that proves
     // the NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
     // one proves NXDOMAIN and the other proves non existence wildcard.
-    query.process(list, Name("a.t.example.com"), RRType::A(),
+    query.process(*list_, Name("a.t.example.com"), RRType::A(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1669,24 +1831,26 @@ TEST_F(QueryTest, wildcardEmptyWithNSEC) {
  * This tests that when there's no SOA and we need a negative answer. It should
  * throw in that case.
  */
-TEST_F(QueryTest, noSOA) {
+TEST_F(QueryTestForMockOnly, noSOA) {
+    // This is a broken data source scenario; works only with mock.
+
     // disable zone's SOA RR.
     mock_finder->setSOAFlag(false);
 
     // The NX Domain
-    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
                                qtype, response), Query::NoSOA);
     // Of course, we don't look into the response, as it throwed
 
     // NXRRSET
-    EXPECT_THROW(query.process(list, Name("nxrrset.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("nxrrset.example.com"),
                                qtype, response), Query::NoSOA);
 }
 
-TEST_F(QueryTest, noMatchZone) {
+TEST_P(QueryTest, noMatchZone) {
     // there's a zone in the memory datasource but it doesn't match the qname.
     // should result in REFUSED.
-    query.process(list, Name("example.org"), qtype, response);
+    query.process(*list_, Name("example.org"), qtype, response);
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
@@ -1696,8 +1860,8 @@ TEST_F(QueryTest, noMatchZone) {
  * The MX RRset has two RRs, one pointing to a known domain with
  * A record, other to unknown out of zone one.
  */
-TEST_F(QueryTest, MX) {
-    query.process(list, Name("mx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MX) {
+    query.process(*list_, Name("mx.example.com"), RRType::MX(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
@@ -1710,8 +1874,8 @@ TEST_F(QueryTest, MX) {
  *
  * This should not trigger the additional processing for the exchange.
  */
-TEST_F(QueryTest, MXAlias) {
-    query.process(list, Name("cnamemx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MXAlias) {
+    query.process(*list_, Name("cnamemx.example.com"), RRType::MX(),
                   response);
 
     // there shouldn't be no additional RRs for the exchanges (we have 3
@@ -1730,69 +1894,69 @@ TEST_F(QueryTest, MXAlias) {
  * TODO: We currently don't do chaining, so only the CNAME itself should be
  * returned.
  */
-TEST_F(QueryTest, CNAME) {
-    query.process(list, Name("cname.example.com"), RRType::A(),
+TEST_P(QueryTest, CNAME) {
+    query.process(*list_, Name("cname.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
         cname_txt, NULL, NULL);
 }
 
-TEST_F(QueryTest, explicitCNAME) {
+TEST_P(QueryTest, explicitCNAME) {
     // same owner name as the CNAME test but explicitly query for CNAME RR.
     // expect the same response as we don't provide a full chain yet.
-    query.process(list, Name("cname.example.com"), RRType::CNAME(),
+    query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
         cname_txt, zone_ns_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, CNAME_NX_RRSET) {
+TEST_P(QueryTest, CNAME_NX_RRSET) {
     // Leads to www.example.com, it doesn't have TXT
     // note: with chaining, what should be expected is not trivial:
     // BIND 9 returns the CNAME in answer and SOA in authority, no additional.
     // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
-    query.process(list, Name("cname.example.com"), RRType::TXT(),
+    query.process(*list_, Name("cname.example.com"), RRType::TXT(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
         cname_txt, NULL, NULL);
 }
 
-TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+TEST_P(QueryTest, explicitCNAME_NX_RRSET) {
     // same owner name as the NXRRSET test but explicitly query for CNAME RR.
-    query.process(list, Name("cname.example.com"), RRType::CNAME(),
+    query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
         cname_txt, zone_ns_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+TEST_P(QueryTest, CNAME_NX_DOMAIN) {
     // Leads to nxdomain.example.com
     // note: with chaining, what should be expected is not trivial:
     // BIND 9 returns the CNAME in answer and SOA in authority, no additional,
     // RCODE being NXDOMAIN.
     // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
     // RCODE being NOERROR.
-    query.process(list, Name("cnamenxdom.example.com"), RRType::A(),
+    query.process(*list_, Name("cnamenxdom.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
         cname_nxdom_txt, NULL, NULL);
 }
 
-TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+TEST_P(QueryTest, explicitCNAME_NX_DOMAIN) {
     // same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
-    query.process(list, Name("cnamenxdom.example.com"),
+    query.process(*list_, Name("cnamenxdom.example.com"),
                   RRType::CNAME(), response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
         cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
 }
 
-TEST_F(QueryTest, CNAME_OUT) {
+TEST_P(QueryTest, CNAME_OUT) {
     /*
      * This leads out of zone. This should have only the CNAME even
      * when we do chaining.
@@ -1801,16 +1965,16 @@ TEST_F(QueryTest, CNAME_OUT) {
      * Then the same test should be done with .org included there and
      * see what it does (depends on what we want to do)
      */
-    query.process(list, Name("cnameout.example.com"), RRType::A(),
+    query.process(*list_, Name("cnameout.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
         cname_out_txt, NULL, NULL);
 }
 
-TEST_F(QueryTest, explicitCNAME_OUT) {
+TEST_P(QueryTest, explicitCNAME_OUT) {
     // same owner name as the OUT test but explicitly query for CNAME RR.
-    query.process(list, Name("cnameout.example.com"), RRType::CNAME(),
+    query.process(*list_, Name("cnameout.example.com"), RRType::CNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1825,8 +1989,8 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
  * as well. This includes tests pointing inside the zone, outside the zone,
  * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
  */
-TEST_F(QueryTest, DNAME) {
-    query.process(list, Name("www.dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME) {
+    query.process(*list_, Name("www.dname.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1841,8 +2005,8 @@ TEST_F(QueryTest, DNAME) {
  * ANY is handled specially sometimes. We check it is not the case with
  * DNAME.
  */
-TEST_F(QueryTest, DNAME_ANY) {
-    query.process(list, Name("www.dname.example.com"), RRType::ANY(),
+TEST_P(QueryTest, DNAME_ANY) {
+    query.process(*list_, Name("www.dname.example.com"), RRType::ANY(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1850,8 +2014,8 @@ TEST_F(QueryTest, DNAME_ANY) {
 }
 
 // Test when we ask for DNAME explicitly, it does no synthetizing.
-TEST_F(QueryTest, explicitDNAME) {
-    query.process(list, Name("dname.example.com"), RRType::DNAME(),
+TEST_P(QueryTest, explicitDNAME) {
+    query.process(*list_, Name("dname.example.com"), RRType::DNAME(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1862,8 +2026,8 @@ TEST_F(QueryTest, explicitDNAME) {
  * Request a RRset at the domain with DNAME. It should not synthetize
  * the CNAME, it should return the RRset.
  */
-TEST_F(QueryTest, DNAME_A) {
-    query.process(list, Name("dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME_A) {
+    query.process(*list_, Name("dname.example.com"), RRType::A(),
                   response);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1874,8 +2038,8 @@ TEST_F(QueryTest, DNAME_A) {
  * Request a RRset at the domain with DNAME that is not there (NXRRSET).
  * It should not synthetize the CNAME.
  */
-TEST_F(QueryTest, DNAME_NX_RRSET) {
-    EXPECT_NO_THROW(query.process(list, Name("dname.example.com"),
+TEST_P(QueryTest, DNAME_NX_RRSET) {
+    EXPECT_NO_THROW(query.process(*list_, Name("dname.example.com"),
                     RRType::TXT(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
@@ -1887,7 +2051,7 @@ TEST_F(QueryTest, DNAME_NX_RRSET) {
  * however, should not throw (and crash the server), but respond with
  * YXDOMAIN.
  */
-TEST_F(QueryTest, LongDNAME) {
+TEST_P(QueryTest, LongDNAME) {
     // A name that is as long as it can be
     Name longname(
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
@@ -1895,7 +2059,7 @@ TEST_F(QueryTest, LongDNAME) {
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "dname.example.com.");
-    EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+    EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
                     response));
 
     responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
@@ -1907,14 +2071,14 @@ TEST_F(QueryTest, LongDNAME) {
  * This tests that we don't reject valid one by some kind of off by
  * one mistake.
  */
-TEST_F(QueryTest, MaxLenDNAME) {
+TEST_P(QueryTest, MaxLenDNAME) {
     Name longname(
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
         "dname.example.com.");
-    EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+    EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
                     response));
 
     // Check the answer is OK
@@ -1963,7 +2127,10 @@ nsec3Check(bool expected_matched, uint8_t expected_labels,
                 actual_rrsets.end());
 }
 
-TEST_F(QueryTest, findNSEC3) {
+TEST_F(QueryTestForMockOnly, findNSEC3) {
+    // This test is intended to test the mock data source behavior; no need
+    // to do it for others.
+
     // In all test cases in the recursive mode, the closest encloser is the
     // apex, and result's closest_labels should be the number of apex labels.
     // (In non recursive mode closest_labels should be the # labels of the
@@ -1975,7 +2142,7 @@ TEST_F(QueryTest, findNSEC3) {
     {
         SCOPED_TRACE("apex, non recursive");
         nsec3Check(true, expected_closest_labels,
-                   nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+                   string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
                    mock_finder->findNSEC3(Name("example.com"), false));
     }
 
@@ -1983,7 +2150,7 @@ TEST_F(QueryTest, findNSEC3) {
     {
         SCOPED_TRACE("apex, recursive");
         nsec3Check(true, expected_closest_labels,
-                   nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+                   string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
                    mock_finder->findNSEC3(Name("example.com"), true));
     }
 
@@ -1992,7 +2159,7 @@ TEST_F(QueryTest, findNSEC3) {
     {
         SCOPED_TRACE("nxdomain, non recursive");
         nsec3Check(false, 4,
-                   nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+                   string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
                    mock_finder->findNSEC3(Name("nxdomain.example.com"),
                                           false));
     }
@@ -2002,7 +2169,7 @@ TEST_F(QueryTest, findNSEC3) {
     {
         SCOPED_TRACE("nxdomain, recursive");
         nsec3Check(true, expected_closest_labels,
-                   nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+                   string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt + "\n" +
                    nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
                    mock_finder->findNSEC3(Name("nxdomain.example.com"), true));
     }
@@ -2012,7 +2179,8 @@ TEST_F(QueryTest, findNSEC3) {
     {
         SCOPED_TRACE("nxdomain, next closer != qname");
         nsec3Check(true, expected_closest_labels,
-                   nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+                   string(nsec3_apex_txt) + "\n" +
+                   nsec3_apex_rrsig_txt + "\n" +
                    nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
                    mock_finder->findNSEC3(Name("nx.domain.example.com"),
                                           true));
@@ -2022,14 +2190,14 @@ TEST_F(QueryTest, findNSEC3) {
     {
         SCOPED_TRACE("largest");
         nsec3Check(false, 4,
-                   nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+                   string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
                    mock_finder->findNSEC3(Name("nxdomain2.example.com"),
                                           false));
     }
     {
         SCOPED_TRACE("smallest");
         nsec3Check(false, 4,
-                   nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+                   string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
                    mock_finder->findNSEC3(Name("nxdomain3.example.com"),
                                           false));
     }
@@ -2100,14 +2268,17 @@ private:
     const bool have_ds_;
 };
 
-TEST_F(QueryTest, dsAboveDelegation) {
+TEST_F(QueryTestForMockOnly, dsAboveDelegation) {
+    // We could setup the child zone for other data sources, but it won't be
+    // simple addition.  For now we test it for mock only.
+
     // Pretending to have authority for the child zone, too.
     memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
                                             Name("delegation.example.com"))));
 
     // The following will succeed only if the search goes to the parent
     // zone, not the child one we added above.
-    EXPECT_NO_THROW(query.process(list,
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("delegation.example.com"),
                                   RRType::DS(), response, true));
 
@@ -2121,7 +2292,7 @@ TEST_F(QueryTest, dsAboveDelegation) {
                   ns_addrs_and_sig_txt.c_str());
 }
 
-TEST_F(QueryTest, dsAboveDelegationNoData) {
+TEST_P(QueryTest, dsAboveDelegationNoData) {
     // Similar to the previous case, but the query is for an unsigned zone
     // (which doesn't have a DS at the parent).  The response should be a
     // "no data" error.  The query should still be handled at the parent.
@@ -2131,7 +2302,7 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
 
     // The following will succeed only if the search goes to the parent
     // zone, not the child one we added above.
-    EXPECT_NO_THROW(query.process(list,
+    EXPECT_NO_THROW(query.process(*list_,
                                   Name("unsigned-delegation.example.com"),
                                   RRType::DS(), response, true));
 
@@ -2148,8 +2319,8 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
 // This one checks that type-DS query results in a "no data" response
 // when it happens to be sent to the child zone, as described in RFC 4035,
 // section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
-TEST_F(QueryTest, dsBelowDelegation) {
-    EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegation) {
+    EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
                                   RRType::DS(), response, true));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -2164,9 +2335,10 @@ TEST_F(QueryTest, dsBelowDelegation) {
 // Similar to the previous case, but even more pathological: the DS somehow
 // exists in the child zone.  The Query module should still return SOA.
 // In our implementation NSEC/NSEC3 isn't attached in this case.
-TEST_F(QueryTest, dsBelowDelegationWithDS) {
-    mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
-    EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegationWithDS) {
+    rrsets_to_add_.push_back(zone_ds_txt);
+    addRRsets(rrsets_to_add_, *list_, base_zone_file);
+    EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
                                   RRType::DS(), response, true));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -2178,16 +2350,16 @@ TEST_F(QueryTest, dsBelowDelegationWithDS) {
 // DS query received at a completely irrelevant (neither parent nor child)
 // server.  It should just like the "noZone" test case, but DS query involves
 // special processing, so we test it explicitly.
-TEST_F(QueryTest, dsNoZone) {
-    query.process(list, Name("example"), RRType::DS(), response,
+TEST_P(QueryTest, dsNoZone) {
+    query.process(*list_, Name("example"), RRType::DS(), response,
                   true);
     responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
 }
 
 // DS query for a "grandchild" zone.  This should result in normal
 // delegation (unless this server also has authority of the grandchild zone).
-TEST_F(QueryTest, dsAtGrandParent) {
-    query.process(list, Name("grand.delegation.example.com"),
+TEST_P(QueryTest, dsAtGrandParent) {
+    query.process(*list_, Name("grand.delegation.example.com"),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
                   (string(delegation_txt) + string(delegation_ds_txt) +
@@ -2201,12 +2373,15 @@ TEST_F(QueryTest, dsAtGrandParent) {
 // side and should result in no data with SOA.  Note that the server doesn't
 // have authority for the "parent".  Unlike the dsAboveDelegation test case
 // the query should be handled in the child zone, not in the grandparent.
-TEST_F(QueryTest, dsAtGrandParentAndChild) {
+TEST_F(QueryTestForMockOnly, dsAtGrandParentAndChild) {
+    // We could setup the child zone for other data sources, but it won't be
+    // simple addition.  For now we test it for mock only.
+
     // Pretending to have authority for the child zone, too.
     const Name childname("grand.delegation.example.com");
     memory_client.addZone(ZoneFinderPtr(
                               new AlternateZoneFinder(childname)));
-    query.process(list, childname, RRType::DS(), response, true);
+    query.process(*list_, childname, RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
                   (childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
                    childname.toText() + " 3600 IN RRSIG " +
@@ -2220,11 +2395,14 @@ TEST_F(QueryTest, dsAtGrandParentAndChild) {
 // DS query for the root name (quite pathological).  Since there's no "parent",
 // the query will be handled in the root zone anyway, and should (normally)
 // result in no data.
-TEST_F(QueryTest, dsAtRoot) {
+TEST_F(QueryTestForMockOnly, dsAtRoot) {
+    // We could setup the additional zone for other data sources, but it
+    // won't be simple addition.  For now we test it for mock only.
+
     // Pretend to be a root server.
     memory_client.addZone(ZoneFinderPtr(
                               new AlternateZoneFinder(Name::ROOT_NAME())));
-    query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+    query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
                   true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
                   (string(". 3600 IN SOA . . 0 0 0 0 0\n") +
@@ -2237,11 +2415,14 @@ TEST_F(QueryTest, dsAtRoot) {
 // Even more pathological case: A faked root zone actually has its own DS
 // query.  How we respond wouldn't matter much in practice, but check if
 // it behaves as it's intended.  This implementation should return the DS.
-TEST_F(QueryTest, dsAtRootWithDS) {
+TEST_F(QueryTestForMockOnly, dsAtRootWithDS) {
+    // We could setup the additional zone for other data sources, but it
+    // won't be simple addition.  For now we test it for mock only.
+
     memory_client.addZone(ZoneFinderPtr(
                               new AlternateZoneFinder(Name::ROOT_NAME(),
                                                       true)));
-    query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+    query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
                   true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
                   (string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
@@ -2253,19 +2434,19 @@ TEST_F(QueryTest, dsAtRootWithDS) {
 }
 
 // Check the signature is present when an NXRRSET is returned
-TEST_F(QueryTest, nxrrsetWithNSEC3) {
-    mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3) {
+    enableNSEC3(rrsets_to_add_);
 
     // NXRRSET with DNSSEC proof.  We should have SOA, NSEC3 that proves the
     // NXRRSET and their RRSIGs.
-    query.process(list, Name("www.example.com"), RRType::TXT(),
+    query.process(*list_, Name("www.example.com"), RRType::TXT(),
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
                   (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec3_www_txt) + "\n" +
-                   mock_finder->hash_map_[Name("www.example.com.")] +
+                   nsec3_hash_.calculate(Name("www.example.com.")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n").c_str(),
                   NULL, mock_finder->getOrigin());
@@ -2273,7 +2454,9 @@ TEST_F(QueryTest, nxrrsetWithNSEC3) {
 
 // Check the exception is correctly raised when the NSEC3 thing isn't in the
 // zone
-TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+TEST_F(QueryTestForMockOnly, nxrrsetMissingNSEC3) {
+    // This is a broken data source scenario; works only with mock.
+
     mock_finder->setNSEC3Flag(true);
     // We just need it to return false for "matched". This indicates
     // there's no exact match for NSEC3 on www.example.com.
@@ -2281,67 +2464,67 @@ TEST_F(QueryTest, nxrrsetMissingNSEC3) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(list, Name("www.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("www.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
 
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
-    mock_finder->addRecord(unsigned_delegation_nsec3_txt);
-    mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_exact) {
+    rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+    enableNSEC3(rrsets_to_add_);
 
     // This delegation has no DS, but does have a matching NSEC3 record
     // (See RFC5155 section 7.2.4)
-    query.process(list, Name("unsigned-delegation.example.com."),
+    query.process(*list_, Name("unsigned-delegation.example.com."),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
                   (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(unsigned_delegation_nsec3_txt) + "\n" +
-                   mock_finder->
-                        hash_map_[Name("unsigned-delegation.example.com.")] +
+                   nsec3_hash_.calculate(
+                       Name("unsigned-delegation.example.com.")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n").c_str(),
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
-    mock_finder->addRecord(unsigned_delegation_nsec3_txt);
-    mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
+    rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+    enableNSEC3(rrsets_to_add_);
 
     // This delegation has no DS, and no directly matching NSEC3 record
     // So the response should contain closest encloser proof (and the
     // 'next closer' should have opt-out set, though that is not
     // actually checked)
     // (See RFC5155 section 7.2.4)
-    query.process(list, Name("unsigned-delegation-optout.example.com."),
+    query.process(*list_, Name("unsigned-delegation-optout.example.com."),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
                   (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec3_apex_txt) + "\n" +
-                   mock_finder->hash_map_[Name("example.com.")] +
+                   nsec3_hash_.calculate(Name("example.com.")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n" +
                    string(unsigned_delegation_nsec3_txt) + "\n" +
-                   mock_finder->
-                        hash_map_[Name("unsigned-delegation.example.com.")] +
+                   nsec3_hash_.calculate(
+                       Name("unsigned-delegation.example.com.")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n").c_str(),
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
+TEST_P(QueryTest, nxdomainWithNSEC3Proof) {
     // Name Error (NXDOMAIN) case with NSEC3 proof per RFC5155 Section 7.2.2.
 
-    // Enable NSEC3
-    mock_finder->setNSEC3Flag(true);
     // This will be the covering NSEC3 for the next closer
-    mock_finder->addRecord(nsec3_uwild_txt);
+    rrsets_to_add_.push_back(nsec3_uwild_txt);
     // This will be the covering NSEC3 for the possible wildcard
-    mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+    rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+    // Enable NSEC3
+    enableNSEC3(rrsets_to_add_);
 
-    query.process(list, Name("nxdomain.example.com"), qtype,
+    query.process(*list_, Name("nxdomain.example.com"), qtype,
                   response, true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
                   // SOA + its RRSIG
@@ -2350,24 +2533,26 @@ TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
                    getCommonRRSIGText("SOA") + "\n" +
                    // NSEC3 for the closest encloser + its RRSIG
                    string(nsec3_apex_txt) + "\n" +
-                   mock_finder->hash_map_[mock_finder->getOrigin()] +
+                   nsec3_hash_.calculate(mock_finder->getOrigin()) +
                    string(".example.com. 3600 IN RRSIG ") +
                    getCommonRRSIGText("NSEC3") + "\n" +
                    // NSEC3 for the next closer + its RRSIG
                    string(nsec3_uwild_txt) + "\n" +
-                   mock_finder->hash_map_[Name("uwild.example.com")] +
+                   nsec3_hash_.calculate(Name("uwild.example.com")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3") + "\n" +
                    // NSEC3 for the wildcard + its RRSIG
                    string(unsigned_delegation_nsec3_txt) +
-                   mock_finder->hash_map_[
-                       Name("unsigned-delegation.example.com")] +
+                   nsec3_hash_.calculate(
+                       Name("unsigned-delegation.example.com")) +
                    ".example.com. 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC3")).c_str(),
                   NULL, mock_finder->getOrigin());
 }
 
-TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadNextNSEC3Proof) {
+    // This is a broken data source scenario; works only with mock.
+
     // Similar to the previous case, but emulating run time collision by
     // returning NULL in the next closer proof for the closest encloser
     // proof.
@@ -2376,12 +2561,14 @@ TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3);
 
-    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+    EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
                                RRType::TXT(), response, true),
                  Query::BadNSEC3);
 }
 
-TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadWildcardNSEC3Proof) {
+    // This is a broken data source scenario; works only with mock.
+
     // Similar to nxdomainWithNSEC3Proof, but let findNSEC3() return a matching
     // NSEC3 for the possible wildcard name, emulating run-time collision.
     // This should result in BadNSEC3 exception.
@@ -2395,7 +2582,7 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
                                       ConstRRsetPtr());
     mock_finder->setNSEC3Result(&nsec3, &wname);
 
-    EXPECT_THROW(query.process(list, Name("nxdomain.example.com"), qtype,
+    EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"), qtype,
                                response, true),
                  Query::BadNSEC3);
 }
@@ -2403,10 +2590,13 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
 // The following are tentative tests until we really add tests for the
 // query logic for these cases.  At that point it's probably better to
 // clean them up.
-TEST_F(QueryTest, emptyNameWithNSEC3) {
-    mock_finder->setNSEC3Flag(true);
-    ZoneFinderContextPtr result = mock_finder->find(
-        Name("no.example.com"), RRType::A(), ZoneFinder::FIND_DNSSEC);
+TEST_P(QueryTest, emptyNameWithNSEC3) {
+    enableNSEC3(rrsets_to_add_);
+    const Name qname("no.example.com");
+    ASSERT_TRUE(list_->find(qname).finder_);
+    ZoneFinderContextPtr result =
+        list_->find(qname).finder_->find(qname, RRType::A(),
+                                         ZoneFinder::FIND_DNSSEC);
     EXPECT_EQ(ZoneFinder::NXRRSET, result->code);
     EXPECT_FALSE(result->rrset);
     EXPECT_TRUE(result->isNSEC3Signed());
@@ -2447,7 +2637,9 @@ loadRRsetVector() {
                loadRRsetVectorCallback);
 }
 
-TEST_F(QueryTest, DuplicateNameRemoval) {
+// Note: this is an independent test; don't have to be in the QueryTest
+// fixture.
+TEST(QueryTestSingle, DuplicateNameRemoval) {
 
     // Load some RRsets into the master vector.
     loadRRsetVector();
diff --git a/src/bin/auth/tests/testdata/.gitignore b/src/bin/auth/tests/testdata/.gitignore
index b2e0e50..37acc8a 100644
--- a/src/bin/auth/tests/testdata/.gitignore
+++ b/src/bin/auth/tests/testdata/.gitignore
@@ -6,3 +6,13 @@
 /shortanswer_fromWire.wire
 /simplequery_fromWire.wire
 /simpleresponse_fromWire.wire
+/example-base.sqlite3
+/example-base.sqlite3.copied
+/example-base.zone
+/example-base.zone
+/example-common-inc.zone
+/example-nsec3-inc.zone
+/example-nsec3.sqlite3
+/example-nsec3.sqlite3.copied
+/example-nsec3.zone
+/example.zone
diff --git a/src/bin/auth/tests/testdata/Makefile.am b/src/bin/auth/tests/testdata/Makefile.am
index a4ea1a5..fed498a 100644
--- a/src/bin/auth/tests/testdata/Makefile.am
+++ b/src/bin/auth/tests/testdata/Makefile.am
@@ -1,4 +1,6 @@
-CLEANFILES = *.wire
+CLEANFILES = *.wire *.copied
+CLEANFILES += example-base.sqlite3 example-nsec3.sqlite3
+CLEANFILES += example-common-inc.zone
 
 BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
 BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
@@ -24,5 +26,8 @@ EXTRA_DIST += example.com
 EXTRA_DIST += example.zone
 EXTRA_DIST += example.sqlite3
 
+EXTRA_DIST += example-base-inc.zone example-nsec3-inc.zone
+EXTRA_DIST += example-common-inc-template.zone
+
 .spec.wire:
 	$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/bin/auth/tests/testdata/example-base-inc.zone b/src/bin/auth/tests/testdata/example-base-inc.zone
new file mode 100644
index 0000000..bbcbef1
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base-inc.zone
@@ -0,0 +1,236 @@
+;; This file defines a set of RRs commonly used in query tests in the
+;; form of standard master zone file.
+;;
+;; It's a sequence of the following pattern:
+;; ;var=<var_name>
+;; RR_1
+;; RR_2
+;; ..
+;; RR_n
+;;
+;; where var_name is a string that can be used as a variable name in a
+;; C/C++ source file or an empty string.  RR_x is a single-line
+;; textual representation of an arbitrary DNS RR.
+;;
+;; If var_name is non empty, the generator script will define a C
+;; variable of C-string type for that set of RRs so that it can be referred
+;; to in the test source file.
+;;
+;; Note that lines beginning ';var=' is no different from other
+;; comment lines as a zone file.  It has special meaning only for the
+;; generator script.  Obviously, real comment lines cannot begin with
+;; ';var=' (which should be less likely to happen in practice though).
+;;
+;; These RRs will be loaded into in-memory data source in that order.
+;; Note that it may impose stricter restriction on the order of RRs.
+;; In general, each RRset of the same name and type and its RRSIG (if
+;; any) is expected to be grouped.
+
+;var=soa_txt
+example.com. 3600 IN SOA . . 1 0 0 0 0
+;var=zone_ns_txt
+example.com. 3600 IN NS glue.delegation.example.com.
+example.com. 3600 IN NS noglue.example.com.
+example.com. 3600 IN NS example.net.
+
+;var=
+example.com. 3600 IN RRSIG SOA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+example.com. 3600 IN RRSIG NS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Note: the position of the next RR is tricky.  It's placed here to
+;; be grouped with the subsequent A RR of the name.  But we also want
+;; to group the A RR with other RRs of a different owner name, so the RRSIG
+;; cannot be placed after the A RR.  The empty 'var=' specification is
+;; not necessary here, but in case we want to reorganize the ordering
+;; (in which case it's more likely to be needed), we keep it here.
+;var=
+noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=ns_addrs_txt
+noglue.example.com. 3600 IN A 192.0.2.53
+glue.delegation.example.com. 3600 IN A 192.0.2.153
+glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
+
+;var=
+glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=delegation_txt
+delegation.example.com. 3600 IN NS glue.delegation.example.com.
+delegation.example.com. 3600 IN NS noglue.example.com.
+delegation.example.com. 3600 IN NS cname.example.com.
+delegation.example.com. 3600 IN NS example.org.
+
+;var=
+delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Borrowed from the RFC4035
+;var=delegation_ds_txt
+delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
+;var=mx_txt
+mx.example.com. 3600 IN MX 10 www.example.com.
+mx.example.com. 3600 IN MX 20 mailer.example.org.
+mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
+;var=www_a_txt
+www.example.com. 3600 IN A 192.0.2.80
+
+;var=
+www.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cname_txt
+cname.example.com. 3600 IN CNAME www.example.com.
+;var=cname_nxdom_txt
+cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
+;; CNAME Leading out of zone
+;var=cname_out_txt
+cnameout.example.com. 3600 IN CNAME www.example.org.
+;; The DNAME to do tests against
+;var=dname_txt
+dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
+;; Some data at the dname node (allowed by RFC 2672)
+;var=dname_a_txt
+dname.example.com. 3600 IN A 192.0.2.5
+;; This is not inside the zone, this is created at runtime
+;; www.dname.example.com. 3600 IN CNAME www.somethinglong.dnametarget.example.com.
+;; The rest of data won't be referenced from the test cases.
+;var=other_zone_rrs
+cnamemailer.example.com. 3600 IN CNAME www.example.com.
+cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
+mx.delegation.example.com. 3600 IN A 192.0.2.100
+;; Wildcards
+;var=wild_txt
+*.wild.example.com. 3600 IN A 192.0.2.7
+;var=nsec_wild_txt
+*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
+
+;var=
+*.wild.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.wild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cnamewild_txt
+*.cnamewild.example.com. 3600 IN CNAME www.example.org.
+;var=nsec_cnamewild_txt
+*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
+
+;var=
+*.cnamewild.example.com. 3600 IN RRSIG CNAME 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.cnamewild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Wildcard_nxrrset
+;var=wild_txt_nxrrset
+*.uwild.example.com. 3600 IN A 192.0.2.9
+;var=nsec_wild_txt_nxrrset
+*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
+;var=
+*.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=wild_txt_next
+www.uwild.example.com. 3600 IN A 192.0.2.11
+;var=
+www.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_wild_txt_next
+www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
+;; Wildcard empty
+;var=empty_txt
+b.*.t.example.com. 3600 IN A 192.0.2.13
+;var=nsec_empty_txt
+b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
+
+;var=
+b.*.t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=empty_prev_txt
+t.example.com. 3600 IN A 192.0.2.15
+;var=nsec_empty_prev_txt
+t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
+
+;var=
+t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Used in NXDOMAIN proof test.  We are going to test some unusual case where
+;; the best possible wildcard is below the "next domain" of the NSEC RR that
+;; proves the NXDOMAIN, i.e.,
+;; mx.example.com. (exist)
+;; (.no.example.com. (qname, NXDOMAIN)
+;; ).no.example.com. (exist)
+;; *.no.example.com. (best possible wildcard, not exist)
+;var=no_txt
+\).no.example.com. 3600 IN AAAA 2001:db8::53
+;; NSEC records.
+;var=nsec_apex_txt
+example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
+;var=
+example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_mx_txt
+mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
+
+;var=
+mx.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=nsec_no_txt
+\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
+
+;var=
+\).no.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
+;; non existence of wildcard.  The following records will be used for that
+;; test.
+;; ).no.example.com. (exist, whose NSEC proves everything)
+;; *.no.example.com. (best possible wildcard, not exist)
+;; nx.no.example.com. (NXDOMAIN)
+;; nz.no.example.com. (exist)
+;var=nz_txt
+nz.no.example.com. 3600 IN AAAA 2001:db8::5300
+;var=nsec_nz_txt
+nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
+;var=nsec_nxdomain_txt
+noglue.example.com. 3600 IN NSEC nonsec.example.com. A
+
+;var=
+noglue.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; NSEC for the normal NXRRSET case
+;var=nsec_www_txt
+www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
+
+;var=
+www.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Authoritative data without NSEC
+;var=nonsec_a_txt
+nonsec.example.com. 3600 IN A 192.0.2.0
+
+;; (Secure) delegation data; Delegation with DS record
+;var=signed_delegation_txt
+signed-delegation.example.com. 3600 IN NS ns.example.net.
+;var=signed_delegation_ds_txt
+signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
+
+;var=
+signed-delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; (Secure) delegation data; Delegation without DS record (and both NSEC
+;; and NSEC3 denying its existence)
+;var=unsigned_delegation_txt
+unsigned-delegation.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_nsec_txt
+unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
+
+;var=
+unsigned-delegation.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Delegation without DS record, and no direct matching NSEC3 record
+;var=unsigned_delegation_optout_txt
+unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_optout_nsec_txt
+unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
+
+;; (Secure) delegation data; Delegation where the DS lookup will raise an
+;; exception.
+;var=bad_delegation_txt
+bad-delegation.example.com. 3600 IN NS ns.example.net.
+
+;; Delegation from an unsigned parent.  There's no DS, and there's no NSEC
+;; or NSEC3 that proves it.
+;var=nosec_delegation_txt
+nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example-base.zone.in b/src/bin/auth/tests/testdata/example-base.zone.in
new file mode 100644
index 0000000..63d2af0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base.zone.in
@@ -0,0 +1,7 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests.
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example-common-inc-template.zone b/src/bin/auth/tests/testdata/example-common-inc-template.zone
new file mode 100644
index 0000000..d7259bf
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-common-inc-template.zone
@@ -0,0 +1,5 @@
+;;
+;; This is an initial template of part of test zone file used in query test
+;; and expected to be included from other zone files.  This is
+;; intentionally kept empty.
+;;
diff --git a/src/bin/auth/tests/testdata/example-nsec3-inc.zone b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
new file mode 100644
index 0000000..7742df0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
@@ -0,0 +1,16 @@
+;; See query_testzone_data.txt for general notes.
+
+;; NSEC3PARAM.  This is needed for database-based data source to
+;; signal the zone is NSEC3-signed
+;var=
+example.com. 3600 IN NSEC3PARAM 1 1 12 aabbccdd
+
+;; NSEC3 RRs.  You may also need to add mapping to MockZoneFinder::hash_map_.
+;var=nsec3_apex_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
+;var=nsec3_apex_rrsig_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec3_www_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+;var=nsec3_www_rrsig_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
diff --git a/src/bin/auth/tests/testdata/example-nsec3.zone.in b/src/bin/auth/tests/testdata/example-nsec3.zone.in
new file mode 100644
index 0000000..aaf3d6a
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3.zone.in
@@ -0,0 +1,8 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests including NSEC3 records, making the zone is "NSEC3 signed".
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_srcdir@/example-nsec3-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example.zone b/src/bin/auth/tests/testdata/example.zone
deleted file mode 100644
index af0b618..0000000
--- a/src/bin/auth/tests/testdata/example.zone
+++ /dev/null
@@ -1,121 +0,0 @@
-;;
-;; This is a complete (but crafted and somewhat broken) zone file used
-;; in query tests.
-;;
-
-example.com. 3600 IN SOA . . 0 0 0 0 0
-example.com. 3600 IN NS glue.delegation.example.com.
-example.com. 3600 IN NS noglue.example.com.
-example.com. 3600 IN NS example.net.
-example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-glue.delegation.example.com. 3600 IN A 192.0.2.153
-glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
-noglue.example.com. 3600 IN A 192.0.2.53
-delegation.example.com. 3600 IN NS glue.delegation.example.com.
-delegation.example.com. 3600 IN NS noglue.example.com.
-delegation.example.com. 3600 IN NS cname.example.com.
-delegation.example.com. 3600 IN NS example.org.
-;; Borrowed from the RFC4035
-delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-mx.example.com. 3600 IN MX 10 www.example.com.
-mx.example.com. 3600 IN MX 20 mailer.example.org.
-mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
-www.example.com. 3600 IN A 192.0.2.80
-cname.example.com. 3600 IN CNAME www.example.com.
-cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
-;; CNAME Leading out of zone
-cnameout.example.com. 3600 IN CNAME www.example.org.
-;; The DNAME to do tests against
-dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
-;; Some data at the dname node (allowed by RFC 2672)
-dname.example.com. 3600 IN A 192.0.2.5
-;; The rest of data won't be referenced from the test cases.
-cnamemailer.example.com. 3600 IN CNAME www.example.com.
-cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
-mx.delegation.example.com. 3600 IN A 192.0.2.100
-;; Wildcards
-*.wild.example.com. 3600 IN A 192.0.2.7
-*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
-*.cnamewild.example.com. 3600 IN CNAME www.example.org.
-*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
-;; Wildcard_nxrrset
-*.uwild.example.com. 3600 IN A 192.0.2.9
-*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
-www.uwild.example.com. 3600 IN A 192.0.2.11
-www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
-;; Wildcard empty
-b.*.t.example.com. 3600 IN A 192.0.2.13
-b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
-t.example.com. 3600 IN A 192.0.2.15
-t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
-;; Used in NXDOMAIN proof test.  We are going to test some unusual case where
-;; the best possible wildcard is below the "next domain" of the NSEC RR that
-;; proves the NXDOMAIN, i.e.,
-;; mx.example.com. (exist)
-;; (.no.example.com. (qname, NXDOMAIN)
-;; ).no.example.com. (exist)
-;; *.no.example.com. (best possible wildcard, not exist)
-\).no.example.com. 3600 IN AAAA 2001:db8::53
-;; NSEC records.
-example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
-mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
-\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
-;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
-;; non existence of wildcard.  The following records will be used for that
-;; test.
-;; ).no.example.com. (exist, whose NSEC proves everything)
-;; *.no.example.com. (best possible wildcard, not exist)
-;; nx.no.example.com. (NXDOMAIN)
-;; nz.no.example.com. (exist)
-nz.no.example.com. 3600 IN AAAA 2001:db8::5300
-nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
-noglue.example.com. 3600 IN NSEC nonsec.example.com. A
-
-;; NSEC for the normal NXRRSET case
-www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
-
-;; Authoritative data without NSEC
-nonsec.example.com. 3600 IN A 192.0.2.0
-
-;; NSEC3 RRs.  You may also need to add mapping to MockZoneFinder::hash_map_.
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-
-;; NSEC3 for wild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for *.uwild.example.com (will be added on demand not to confuse
-;; other tests)
-b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-;; NSEC3 for uwild.example.com. (will be added on demand)
-t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-
-;; (Secure) delegation data; Delegation with DS record
-signed-delegation.example.com. 3600 IN NS ns.example.net.
-signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
-
-;; (Secure) delegation data; Delegation without DS record (and both NSEC
-;; and NSEC3 denying its existence)
-unsigned-delegation.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
-;; This one will be added on demand
-q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG
-
-;; Delegation without DS record, and no direct matching NSEC3 record
-unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
-
-;; (Secure) delegation data; Delegation where the DS lookup will raise an
-;; exception.
-bad-delegation.example.com. 3600 IN NS ns.example.net.
-
-;; Delegation from an unsigned parent.  There's no DS, and there's no NSEC
-;; or NSEC3 that proves it.
-nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example.zone.in b/src/bin/auth/tests/testdata/example.zone.in
new file mode 100644
index 0000000..608c09f
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example.zone.in
@@ -0,0 +1,6 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests, excluding NSEC3 records.
+;;
+
+$INCLUDE @abs_builddir@/example-base.zone
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index 2ce86ab..e32544a 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -160,7 +160,7 @@
 	    <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
             daemon to use.
             The default is
-            <filename>/usr/local/var/bind10-devel/msg_socket</filename>.
+            <filename>/usr/local/var/bind10/msg_socket</filename>.
 <!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
            </para>
          </listitem>
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index aba8da5..9414ed6 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -154,7 +154,7 @@ The boss module received the given signal.
 % BIND10_RESTART_COMPONENT_SKIPPED Skipped restarting a component %1
 The boss module tried to restart a component after it failed (crashed)
 unexpectedly, but the boss then found that the component had been removed
-from its local configuration of components to run.  This is an unusal
+from its local configuration of components to run.  This is an unusual
 situation but can happen if the administrator removes the component from
 the configuration after the component's crash and before the restart time.
 The boss module simply skipped restarting that module, and the whole system
@@ -262,7 +262,7 @@ indicated OS API function with given error.
 The boss forwards a request for a socket to the socket creator.
 
 % BIND10_STARTED_CC started configuration/command session
-Debug message given when BIND 10 has successfull started the object that
+Debug message given when BIND 10 has successfully started the object that
 handles configuration and commands.
 
 % BIND10_STARTED_PROCESS started %1
@@ -308,13 +308,6 @@ During the startup process, a number of messages are exchanged between the
 Boss process and the processes it starts.  This error is output when a
 message received by the Boss process is not recognised.
 
-% BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
-The resolver is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
-
 % BIND10_STOP_PROCESS asking %1 to shut down
 The boss module is sending a shutdown command to the given module over
 the message channel.
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 882653b..6fe5485 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -103,8 +103,31 @@ VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
 # This is for boot_time of Boss
 _BASETIME = time.gmtime()
 
+# Detailed error message commonly used on startup failure, possibly due to
+# permission issue regarding log lock file.  We dump verbose message because
+# it may not be clear exactly what to do if it simply says
+# "failed to open <filename>: permission denied"
+NOTE_ON_LOCK_FILE = """\
+TIP: if this is about permission error for a lock file, check if the directory
+of the file is writable for the user of the bind10 process; often you need
+to start bind10 as a super user.  Also, if you specify the -u option to
+change the user and group, the directory must be writable for the group,
+and the created lock file must be writable for that user. Finally, make sure
+the lock file is not left in the directly before restarting.
+"""
+
 class ProcessInfoError(Exception): pass
 
+class ChangeUserError(Exception):
+    '''Exception raised when setuid/setgid fails.
+
+    When raised, it's expected to be propagated via underlying component
+    management modules to the top level so that it will help provide useful
+    fatal error message.
+
+    '''
+    pass
+
 class ProcessInfo:
     """Information about a process"""
 
@@ -206,8 +229,8 @@ class BoB:
         # restart. Components manage their own restart schedule now
         self.components_to_restart = []
         self.runnable = False
-        self.uid = setuid
-        self.gid = setgid
+        self.__uid = setuid
+        self.__gid = setgid
         self.username = username
         self.verbose = verbose
         self.nokill = nokill
@@ -269,6 +292,31 @@ class BoB:
         # Update the configuration
         self._component_configurator.reconfigure(comps)
 
+    def change_user(self):
+        '''Change the user and group to those specified on construction.
+
+        This method is expected to be called by a component on initial
+        startup when the system is ready to switch the user and group
+        (i.e., once all components that need the privilege of the original
+        user have started).
+        '''
+        try:
+            if self.__gid is not None:
+                logger.info(BIND10_SETGID, self.__gid)
+                posix.setgid(self.__gid)
+        except Exception as ex:
+            raise ChangeUserError('failed to change group: ' + str(ex))
+
+        try:
+            if self.__uid is not None:
+                posix.setuid(self.__uid)
+                # We use one-shot logger after setuid here.  This will
+                # detect any permission issue regarding logging due to the
+                # result of setuid at the earliest opportunity.
+                isc.log.Logger("boss").info(BIND10_SETUID, self.__uid)
+        except Exception as ex:
+            raise ChangeUserError('failed to change user: ' + str(ex))
+
     def config_handler(self, new_config):
         # If this is initial update, don't do anything now, leave it to startup
         if not self.runnable:
@@ -578,8 +626,6 @@ class BoB:
             are pure speculation.  As with the auth daemon, they should be
             read from the configuration database.
         """
-        if self.uid is not None and self.__started:
-            logger.warn(BIND10_START_AS_NON_ROOT_RESOLVER)
         self.curproc = "b10-resolver"
         # XXX: this must be read from the configuration manager in the future
         resargs = ['b10-resolver']
@@ -646,6 +692,9 @@ class BoB:
         try:
             self.c_channel_env = c_channel_env
             self.start_all_components()
+        except ChangeUserError as e:
+            self.kill_started_components()
+            return str(e) + '; ' + NOTE_ON_LOCK_FILE.replace('\n', ' ')
         except Exception as e:
             self.kill_started_components()
             return "Unable to start " + self.curproc + ": " + str(e)
@@ -1155,7 +1204,19 @@ def remove_lock_files():
     for f in lockfiles:
         fname = lpath + '/' + f
         if os.path.isfile(fname):
-            os.unlink(fname)
+            try:
+                os.unlink(fname)
+            except OSError as e:
+                # We catch and ignore permission related error on unlink.
+                # This can happen if bind10 started with -u, created a lock
+                # file as a privileged user, but the directory is not writable
+                # for the changed user.  This setup will cause immediate
+                # start failure, and we leave verbose error message including
+                # the leftover lock file, so it should be acceptable to ignore
+                # it (note that it doesn't make sense to log this event at
+                # this poitn)
+                if e.errno != errno.EPERM and e.errno != errno.EACCES:
+                    raise
 
     return
 
@@ -1173,13 +1234,7 @@ def main():
     except RuntimeError as e:
         sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
                          str(e))
-        sys.stderr.write("""\
-TIP: if this is about permission error for a lock file, check if the directory
-of the file is writable for the user of the bind10 process; often you need
-to start bind10 as a super user.  Also, if you specify the -u option to
-change the user and group, the directory must be writable for the group,
-and the created lock file must be writable for that user.
-""")
+        sys.stderr.write(NOTE_ON_LOCK_FILE)
         sys.exit(1)
 
     # Check user ID.
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 409790c..ccfa831 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -341,6 +341,18 @@ class TestCacheCommands(unittest.TestCase):
 
 
 class TestBoB(unittest.TestCase):
+    def setUp(self):
+        # Save original values that may be tweaked in some tests
+        self.__orig_setgid = bind10_src.posix.setgid
+        self.__orig_setuid = bind10_src.posix.setuid
+        self.__orig_logger_class = isc.log.Logger
+
+    def tearDown(self):
+        # Restore original values saved in setUp()
+        bind10_src.posix.setgid = self.__orig_setgid
+        bind10_src.posix.setuid = self.__orig_setuid
+        isc.log.Logger = self.__orig_logger_class
+
     def test_init(self):
         bob = BoB()
         self.assertEqual(bob.verbose, False)
@@ -349,10 +361,56 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.components, {})
         self.assertEqual(bob.runnable, False)
-        self.assertEqual(bob.uid, None)
         self.assertEqual(bob.username, None)
         self.assertIsNone(bob._socket_cache)
 
+    def __setgid(self, gid):
+        self.__gid_set = gid
+
+    def __setuid(self, uid):
+        self.__uid_set = uid
+
+    def test_change_user(self):
+        bind10_src.posix.setgid = self.__setgid
+        bind10_src.posix.setuid = self.__setuid
+
+        self.__gid_set = None
+        self.__uid_set = None
+        bob = BoB()
+        bob.change_user()
+        # No gid/uid set in boss, nothing called.
+        self.assertIsNone(self.__gid_set)
+        self.assertIsNone(self.__uid_set)
+
+        BoB(setuid=42, setgid=4200).change_user()
+        # This time, it get's called
+        self.assertEqual(4200, self.__gid_set)
+        self.assertEqual(42, self.__uid_set)
+
+        def raising_set_xid(gid_or_uid):
+            ex = OSError()
+            ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+            raise ex
+
+        # Let setgid raise an exception
+        bind10_src.posix.setgid = raising_set_xid
+        bind10_src.posix.setuid = self.__setuid
+        self.assertRaises(bind10_src.ChangeUserError,
+                          BoB(setuid=42, setgid=4200).change_user)
+
+        # Let setuid raise an exception
+        bind10_src.posix.setgid = self.__setgid
+        bind10_src.posix.setuid = raising_set_xid
+        self.assertRaises(bind10_src.ChangeUserError,
+                          BoB(setuid=42, setgid=4200).change_user)
+
+        # Let initial log output after setuid raise an exception
+        bind10_src.posix.setgid = self.__setgid
+        bind10_src.posix.setuid = self.__setuid
+        isc.log.Logger = raising_set_xid
+        self.assertRaises(bind10_src.ChangeUserError,
+                          BoB(setuid=42, setgid=4200).change_user)
+
     def test_set_creator(self):
         """
         Test the call to set_creator. First time, the cache is created
@@ -423,7 +481,6 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.components, {})
         self.assertEqual(bob.runnable, False)
-        self.assertEqual(bob.uid, None)
         self.assertEqual(bob.username, None)
 
     def test_command_handler(self):
@@ -2021,8 +2078,10 @@ class TestBossComponents(unittest.TestCase):
 
             def start_all_components(self):
                 self.started = True
-                if self.throw:
+                if self.throw is True:
                     raise Exception('Assume starting components has failed.')
+                elif self.throw:
+                    raise self.throw
 
             def kill_started_components(self):
                 self.killed = True
@@ -2067,6 +2126,12 @@ class TestBossComponents(unittest.TestCase):
         r = bob.startup()
         self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
 
+        # Check failure of changing user results in a different message
+        bob = MockBobStartup(bind10_src.ChangeUserError('failed to chusr'))
+        r = bob.startup()
+        self.assertIn('failed to chusr', r)
+        self.assertTrue(bob.killed)
+
         # Check the case when socket file already exists
         isc.cc.Session = DummySessionSocketExists
         bob = MockBobStartup(False)
@@ -2277,11 +2342,15 @@ class TestFunctions(unittest.TestCase):
         self.assertFalse(os.path.exists(self.lockfile_testpath))
         os.mkdir(self.lockfile_testpath)
         self.assertTrue(os.path.isdir(self.lockfile_testpath))
+        self.__isfile_orig = bind10_src.os.path.isfile
+        self.__unlink_orig = bind10_src.os.unlink
 
     def tearDown(self):
         os.rmdir(self.lockfile_testpath)
         self.assertFalse(os.path.isdir(self.lockfile_testpath))
         os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+        bind10_src.os.path.isfile = self.__isfile_orig
+        bind10_src.os.unlink = self.__unlink_orig
 
     def test_remove_lock_files(self):
         os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
@@ -2305,6 +2374,28 @@ class TestFunctions(unittest.TestCase):
         # second call should not assert anyway
         bind10_src.remove_lock_files()
 
+    def test_remove_lock_files_fail(self):
+        # Permission error on unlink is ignored; other exceptions are really
+        # unexpected and propagated.
+        def __raising_unlink(unused, ex):
+            raise ex
+
+        bind10_src.os.path.isfile = lambda _: True
+        os_error = OSError()
+        bind10_src.os.unlink = lambda f: __raising_unlink(f, os_error)
+
+        os_error.errno = errno.EPERM
+        bind10_src.remove_lock_files() # no disruption
+
+        os_error.errno = errno.EACCES
+        bind10_src.remove_lock_files() # no disruption
+
+        os_error.errno = errno.ENOENT
+        self.assertRaises(OSError, bind10_src.remove_lock_files)
+
+        bind10_src.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
+        self.assertRaises(Exception, bind10_src.remove_lock_files)
+
     def test_get_signame(self):
         # just test with some samples
         signame = bind10_src.get_signame(signal.SIGTERM)
diff --git a/src/bin/cfgmgr/b10-cfgmgr.xml b/src/bin/cfgmgr/b10-cfgmgr.xml
index ff5706c..d8688f9 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.xml
+++ b/src/bin/cfgmgr/b10-cfgmgr.xml
@@ -136,7 +136,7 @@
   <refsect1>
     <title>FILES</title>
 <!-- TODO: fix path -->
-    <para><filename>/usr/local/var/bind10-devel/b10-config.db</filename>
+    <para><filename>/usr/local/var/bind10/b10-config.db</filename>
       — Configuration storage file.
     </para>
   </refsect1>
diff --git a/src/bin/cmdctl/b10-certgen.xml b/src/bin/cmdctl/b10-certgen.xml
index 1e3c8e3..5ac8783 100644
--- a/src/bin/cmdctl/b10-certgen.xml
+++ b/src/bin/cmdctl/b10-certgen.xml
@@ -190,7 +190,7 @@
       To update an expired certificate in BIND 10 that has been installed to
       /usr/local:
       <screen>
-$> cd /usr/local/etc/bind10-devel/
+$> cd /usr/local/etc/bind10/
 
 $> b10-certgen
 cmdctl-certfile.pem failed to verify: certificate has expired
diff --git a/src/bin/cmdctl/b10-cmdctl.xml b/src/bin/cmdctl/b10-cmdctl.xml
index e01d5a2..4b1b32f 100644
--- a/src/bin/cmdctl/b10-cmdctl.xml
+++ b/src/bin/cmdctl/b10-cmdctl.xml
@@ -147,21 +147,21 @@
       <varname>accounts_file</varname> defines the path to the
       user accounts database.
       The default is
-      <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>.
+      <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>.
     </para>
 
     <para>
       <varname>cert_file</varname> defines the path to the
       PEM certificate file.
       The default is
-      <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>.
+      <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>.
     </para>
 
     <para>
       <varname>key_file</varname> defines the path to the PEM private key
       file.
       The default is
-      <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>.
+      <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>.
     </para>
 
 <!-- TODO: formating -->
@@ -187,17 +187,17 @@
 <!-- TODO: permissions -->
 <!-- TODO: what about multiple accounts? -->
 <!-- TODO: shouldn't the password file name say cmdctl in it? -->
-    <para><filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>
+    <para><filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>
       — account database containing the name, hashed password,
       and the salt.
     </para>
 <!-- TODO: replace /usr/local -->
 <!-- TODO: permissions -->
 <!-- TODO: shouldn't have both in same file, will be configurable -->
-    <para><filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>
+    <para><filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>
       — contains the Private key.
     </para>
-    <para><filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>
+    <para><filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>
       — contains the Certificate.
     </para>
   </refsect1>
diff --git a/src/bin/dbutil/dbutil_messages.mes b/src/bin/dbutil/dbutil_messages.mes
index 90ede92..2fbd000 100644
--- a/src/bin/dbutil/dbutil_messages.mes
+++ b/src/bin/dbutil/dbutil_messages.mes
@@ -53,7 +53,7 @@ inconsistent state, and it is advised to restore it from the backup that was
 created when b10-dbutil started.
 
 % DBUTIL_EXECUTE Executing SQL statement: %1
-Debug message; the given SQL statement is executed
+Debug message; the given SQL statement is executed.
 
 % DBUTIL_FILE Database file: %1
 The database file that is being checked.
@@ -67,7 +67,7 @@ The given database statement failed to execute. The error is shown in the
 message.
 
 % DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected
-There were too many command-line arguments to b10-dbutil
+There were too many command-line arguments to b10-dbutil.
 
 % DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed
 The user aborted the upgrade, and b10-dbutil will now exit.
@@ -95,7 +95,7 @@ again.
 
 % DBUTIL_UPGRADE_PREPARATION_FAILED upgrade preparation failed: %1
 An unexpected error occurred while b10-dbutil was preparing to upgrade the
-database schema. The error is shown in the message
+database schema. The error is shown in the message.
 
 % DBUTIL_UPGRADE_SUCCESFUL database upgrade successfully completed
 The database schema update was completed successfully.
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index c896591..28f08f7 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -58,6 +58,7 @@ b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
 endif
 
 b10_dhcp4_LDADD  = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
@@ -65,6 +66,5 @@ b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 
-
 b10_dhcp4dir = $(pkgdatadir)
 b10_dhcp4_DATA = dhcp4.spec
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 08d16f8..795b61e 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -13,9 +13,14 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config/ccsession.h>
-#include <dhcpsrv/cfgmgr.h>
 #include <dhcp4/config_parser.h>
 #include <dhcp4/dhcp4_log.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/algorithm/string.hpp>
@@ -25,45 +30,63 @@
 #include <map>
 
 using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
 using namespace isc::data;
 using namespace isc::asiolink;
 
-namespace isc {
-namespace dhcp {
+namespace {
 
 /// @brief auxiliary type used for storing element name and its parser
 typedef pair<string, ConstElementPtr> ConfigPair;
 
 /// @brief a factory method that will create a parser for a given element name
-typedef Dhcp4ConfigParser* ParserFactory(const std::string& config_id);
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
 
 /// @brief a collection of factories that creates parsers for specified element names
 typedef std::map<std::string, ParserFactory*> FactoryMap;
 
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<std::string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<std::string, std::string> StringStorage;
+
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
 /// @brief a collection of pools
 ///
 /// That type is used as intermediate storage, when pools are parsed, but there is
 /// no subnet object created yet to store them.
 typedef std::vector<Pool4Ptr> PoolStorage;
 
+/// @brief Collection of option descriptors. This container allows searching for
+/// options using the option code or persistency flag. This is useful when merging
+/// existing options with newly configured options.
+typedef Subnet::OptionContainer OptionStorage;
+
 /// @brief Global uint32 parameters that will be used as defaults.
 Uint32Storage uint32_defaults;
 
 /// @brief global string parameters that will be used as defaults.
 StringStorage string_defaults;
 
+/// @brief Global storage for options that will be used as defaults.
+OptionStorage option_defaults;
+
 /// @brief a dummy configuration parser
 ///
 /// It is a debugging parser. It does not configure anything,
 /// will accept any configuration and will just print it out
 /// on commit. Useful for debugging existing configurations and
 /// adding new ones.
-class DebugParser : public Dhcp4ConfigParser {
+class DebugParser : public DhcpConfigParser {
 public:
 
     /// @brief Constructor
     ///
-    /// See \ref Dhcp4ConfigParser class for details.
+    /// See @ref DhcpConfigParser class for details.
     ///
     /// @param param_name name of the parsed parameter
     DebugParser(const std::string& param_name)
@@ -72,7 +95,7 @@ public:
 
     /// @brief builds parameter value
     ///
-    /// See \ref Dhcp4ConfigParser class for details.
+    /// See @ref DhcpConfigParser class for details.
     ///
     /// @param new_config pointer to the new configuration
     virtual void build(ConstElementPtr new_config) {
@@ -86,7 +109,7 @@ public:
     /// This is a method required by base class. It pretends to apply the
     /// configuration, but in fact it only prints the parameter out.
     ///
-    /// See \ref Dhcp4ConfigParser class for details.
+    /// See @ref DhcpConfigParser class for details.
     virtual void commit() {
         // Debug message. The whole DebugParser class is used only for parser
         // debugging, and is not used in production code. It is very convenient
@@ -98,7 +121,7 @@ public:
     /// @brief factory that constructs DebugParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* Factory(const std::string& param_name) {
         return (new DebugParser(param_name));
     }
 
@@ -110,6 +133,85 @@ private:
     ConstElementPtr value_;
 };
 
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param param_name name of the parameter.
+    BooleanParser(const std::string& param_name)
+        : storage_(NULL),
+          param_name_(param_name),
+          value_(false) {
+        // Empty parameter name is invalid.
+        if (param_name_.empty()) {
+            isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
+    }
+
+    /// @brief Parse a boolean value.
+    ///
+    /// @param value a value to be parsed.
+    ///
+    /// @throw isc::InvalidOperation if a storage has not been set
+    ///        prior to calling this function
+    /// @throw isc::dhcp::Dhcp4ConfigError if a provided parameter's
+    ///        name is empty.
+    virtual void build(ConstElementPtr value) {
+        if (storage_ == NULL) {
+            isc_throw(isc::InvalidOperation, "parser logic error:"
+                      << " storage for the " << param_name_
+                      << " value has not been set");
+        } else if (param_name_.empty()) {
+            isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
+        // The Config Manager checks if user specified a
+        // valid value for a boolean parameter: True or False.
+        // It is then ok to assume that if str() does not return
+        // 'true' the value is 'false'.
+        value_ = (value->str() == "true") ? true : false;
+    }
+
+    /// @brief Put a parsed value to the storage.
+    virtual void commit() {
+        if (storage_ != NULL && !param_name_.empty()) {
+            (*storage_)[param_name_] = value_;
+        }
+    }
+
+    /// @brief Create an instance of the boolean parser.
+    ///
+    /// @param param_name name of the parameter for which the
+    ///        parser is created.
+    static DhcpConfigParser* factory(const std::string& param_name) {
+        return (new BooleanParser(param_name));
+    }
+
+    /// @brief Set the storage for parsed value.
+    ///
+    /// This function must be called prior to calling build.
+    ///
+    /// @param storage a pointer to the storage where parsed data
+    ///        is to be stored.
+    void setStorage(BooleanStorage* storage) {
+        storage_ = storage;
+    }
+
+private:
+    /// Pointer to the storage where parsed value is stored.
+    BooleanStorage* storage_;
+    /// Name of the parameter which value is parsed with this parser.
+    std::string param_name_;
+    /// Parsed value.
+    bool value_;
+};
+
 /// @brief Configuration parser for uint32 parameters
 ///
 /// This class is a generic parser that is able to handle any uint32 integer
@@ -117,27 +219,36 @@ private:
 /// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
 /// in subnet config), it can be pointed to a different storage, using
 /// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref Dhcp4ConfigParser.
+/// in its base class, @ref DhcpConfigParser.
 ///
 /// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class Uint32Parser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class Uint32Parser : public DhcpConfigParser {
 public:
 
     /// @brief constructor for Uint32Parser
     /// @param param_name name of the configuration parameter being parsed
     Uint32Parser(const std::string& param_name)
-        :storage_(&uint32_defaults), param_name_(param_name) {
+        : storage_(&uint32_defaults),
+          param_name_(param_name) {
+        // Empty parameter name is invalid.
+        if (param_name_.empty()) {
+            isc_throw(Dhcp4ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
     }
 
-    /// @brief builds parameter value
-    ///
-    /// Parses configuration entry and stores it in a storage. See
-    /// \ref setStorage() for details.
+    /// @brief Parses configuration configuration parameter as uint32_t.
     ///
     /// @param value pointer to the content of parsed values
     /// @throw BadValue if supplied value could not be base to uint32_t
+    ///        or the parameter name is empty.
     virtual void build(ConstElementPtr value) {
+        if (param_name_.empty()) {
+            isc_throw(Dhcp4ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
+
         int64_t check;
         string x = value->str();
         try {
@@ -157,31 +268,27 @@ public:
 
         // value is small enough to fit
         value_ = static_cast<uint32_t>(check);
-
-        (*storage_)[param_name_] = value_;
     }
 
-    /// @brief does nothing
-    ///
-    /// This method is required for all parsers. The value itself
-    /// is not commited anywhere. Higher level parsers are expected to
-    /// use values stored in the storage, e.g. renew-timer for a given
-    /// subnet is stored in subnet-specific storage. It is not commited
-    /// here, but is rather used by \ref Subnet4ConfigParser when constructing
-    /// the subnet.
+    /// @brief Stores the parsed uint32_t value in a storage.
     virtual void commit() {
+        if (storage_ != NULL && !param_name_.empty()) {
+            // If a given parameter already exists in the storage we override
+            // its value. If it doesn't we insert a new element.
+            (*storage_)[param_name_] = value_;
+        }
     }
 
     /// @brief factory that constructs Uint32Parser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new Uint32Parser(param_name));
     }
 
     /// @brief sets storage for value of this parameter
     ///
-    /// See \ref dhcpv4ConfigInherit for details.
+    /// See @ref dhcpv4ConfigInherit for details.
     ///
     /// @param storage pointer to the storage container
     void setStorage(Uint32Storage* storage) {
@@ -206,47 +313,48 @@ private:
 /// (string_defaults). If used in smaller scopes (e.g. to parse parameters
 /// in subnet config), it can be pointed to a different storage, using
 /// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref Dhcp4ConfigParser.
+/// in its base class, @ref DhcpConfigParser.
 ///
 /// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class StringParser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class StringParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor for StringParser
     /// @param param_name name of the configuration parameter being parsed
     StringParser(const std::string& param_name)
         :storage_(&string_defaults), param_name_(param_name) {
+        // Empty parameter name is invalid.
+        if (param_name_.empty()) {
+            isc_throw(Dhcp4ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
     }
 
     /// @brief parses parameter value
     ///
     /// Parses configuration entry and stores it in storage. See
-    /// \ref setStorage() for details.
+    /// @ref setStorage() for details.
     ///
     /// @param value pointer to the content of parsed values
     virtual void build(ConstElementPtr value) {
         value_ = value->str();
         boost::erase_all(value_, "\"");
-
-        (*storage_)[param_name_] = value_;
     }
 
-    /// @brief does nothing
-    ///
-    /// This method is required for all parser. The value itself
-    /// is not commited anywhere. Higher level parsers are expected to
-    /// use values stored in the storage, e.g. renew-timer for a given
-    /// subnet is stored in subnet-specific storage. It is not commited
-    /// here, but is rather used by its parent parser when constructing
-    /// an object, e.g. the subnet.
+    /// @brief Stores the parsed value in a storage.
     virtual void commit() {
+        if (storage_ != NULL && !param_name_.empty()) {
+            // If a given parameter already exists in the storage we override
+            // its value. If it doesn't we insert a new element.
+            (*storage_)[param_name_] = value_;
+        }
     }
 
     /// @brief factory that constructs StringParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new StringParser(param_name));
     }
 
@@ -279,7 +387,7 @@ private:
 /// designates all interfaces.
 ///
 /// It is useful for parsing Dhcp4/interface parameter.
-class InterfaceListConfigParser : public Dhcp4ConfigParser {
+class InterfaceListConfigParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor
@@ -317,7 +425,7 @@ public:
     /// @brief factory that constructs InterfaceListConfigParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new InterfaceListConfigParser(param_name));
     }
 
@@ -336,7 +444,7 @@ private:
 /// before build(). Otherwise exception will be thrown.
 ///
 /// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
-class PoolParser : public Dhcp4ConfigParser {
+class PoolParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor.
@@ -397,7 +505,7 @@ public:
                 }
 
                 Pool4Ptr pool(new Pool4(addr, len));
-                pools_->push_back(pool);
+                local_pools_.push_back(pool);
                 continue;
             }
 
@@ -410,7 +518,7 @@ public:
 
                 Pool4Ptr pool(new Pool4(min, max));
 
-                pools_->push_back(pool);
+                local_pools_.push_back(pool);
                 continue;
             }
 
@@ -429,17 +537,22 @@ public:
         pools_ = storage;
     }
 
-    /// @brief does nothing.
-    ///
-    /// This method is required for all parsers. The value itself
-    /// is not commited anywhere. Higher level parsers (for subnet) are expected
-    /// to use values stored in the storage.
-    virtual void commit() {}
+    /// @brief Stores the parsed values in a storage provided
+    ///        by an upper level parser.
+    virtual void commit() {
+        if (pools_) {
+            // local_pools_ holds the values produced by the build function.
+            // At this point parsing should have completed successfuly so
+            // we can append new data to the supplied storage.
+            pools_->insert(pools_->end(), local_pools_.begin(),
+                           local_pools_.end());
+        }
+    }
 
     /// @brief factory that constructs PoolParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new PoolParser(param_name));
     }
 
@@ -449,13 +562,405 @@ private:
     /// That is typically a storage somewhere in Subnet parser
     /// (an upper level parser).
     PoolStorage* pools_;
+    /// A temporary storage for pools configuration. It is a
+    /// storage where pools are stored by build function.
+    PoolStorage local_pools_;
+};
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. If parsing is successful then an
+/// instance of an option is created and added to the storage provided
+/// by the calling class.
+///
+/// @todo This class parses and validates the option name. However it is
+/// not used anywhere until support for option spaces is implemented
+/// (see tickets #2319, #2314). When option spaces are implemented
+/// there will be a way to reference the particular option using
+/// its type (code) or option name.
+class OptionDataParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Class constructor.
+    OptionDataParser(const std::string&)
+        : options_(NULL),
+          // initialize option to NULL ptr
+          option_descriptor_(false) { }
+
+    /// @brief Parses the single option data.
+    ///
+    /// This method parses the data of a single option from the configuration.
+    /// The option data includes option name, option code and data being
+    /// carried by this option. Eventually it creates the instance of the
+    /// option.
+    ///
+    /// @warning setStorage must be called with valid storage pointer prior
+    /// to calling this method.
+    ///
+    /// @param option_data_entries collection of entries that define value
+    /// for a particular option.
+    /// @throw Dhcp4ConfigError if invalid parameter specified in
+    /// the configuration.
+    /// @throw isc::InvalidOperation if failed to set storage prior to
+    /// calling build.
+    /// @throw isc::BadValue if option data storage is invalid.
+    virtual void build(ConstElementPtr option_data_entries) {
+        if (options_ == NULL) {
+            isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+                      "parsing option data.");
+        }
+        BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+            ParserPtr parser;
+            if (param.first == "name") {
+                boost::shared_ptr<StringParser>
+                    name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
+                if (name_parser) {
+                    name_parser->setStorage(&string_values_);
+                    parser = name_parser;
+                }
+            } else if (param.first == "code") {
+                boost::shared_ptr<Uint32Parser>
+                    code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
+                if (code_parser) {
+                    code_parser->setStorage(&uint32_values_);
+                    parser = code_parser;
+                }
+            } else if (param.first == "data") {
+                boost::shared_ptr<StringParser>
+                    value_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
+                if (value_parser) {
+                    value_parser->setStorage(&string_values_);
+                    parser = value_parser;
+                }
+            } else if (param.first == "csv-format") {
+                boost::shared_ptr<BooleanParser>
+                    value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
+                if (value_parser) {
+                    value_parser->setStorage(&boolean_values_);
+                    parser = value_parser;
+                }
+            } else {
+                isc_throw(Dhcp4ConfigError,
+                          "Parser error: option-data parameter not supported: "
+                          << param.first);
+            }
+            parser->build(param.second);
+            // Before we can create an option we need to get the data from
+            // the child parsers. The only way to do it is to invoke commit
+            // on them so as they store the values in appropriate storages
+            // that this class provided to them. Note that this will not
+            // modify values stored in the global storages so the configuration
+            // will remain consistent even parsing fails somewhere further on.
+            parser->commit();
+        }
+        // Try to create the option instance.
+        createOption();
+    }
+
+    /// @brief Commits option value.
+    ///
+    /// This function adds a new option to the storage or replaces an existing option
+    /// with the same code.
+    ///
+    /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
+    /// to call build() prior to commit. If that happens data in the storage
+    /// remain un-modified.
+    virtual void commit() {
+        if (options_ == NULL) {
+            isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+                      "commiting option data.");
+        } else  if (!option_descriptor_.option) {
+            // Before we can commit the new option should be configured. If it is not
+            // than somebody must have called commit() before build().
+            isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+                      " thus there is nothing to commit. Has build() been called?");
+        }
+        uint16_t opt_type = option_descriptor_.option->getType();
+        Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+        // Try to find options with the particular option code in the main
+        // storage. If found, remove these options because they will be
+        // replaced with new one.
+        Subnet::OptionContainerTypeRange range =
+            idx.equal_range(opt_type);
+        if (std::distance(range.first, range.second) > 0) {
+            idx.erase(range.first, range.second);
+        }
+        // Append new option to the main storage.
+        options_->push_back(option_descriptor_);
+    }
+
+    /// @brief Set storage for the parser.
+    ///
+    /// Sets storage for the parser. This storage points to the
+    /// vector of options and is used by multiple instances of
+    /// OptionDataParser. Each instance creates exactly one object
+    /// of dhcp::Option or derived type and appends it to this
+    /// storage.
+    ///
+    /// @param storage pointer to the options storage
+    void setStorage(OptionStorage* storage) {
+        options_ = storage;
+    }
+
+private:
+
+    /// @brief Create option instance.
+    ///
+    /// Creates an instance of an option and adds it to the provided
+    /// options storage. If the option data parsed by \ref build function
+    /// are invalid or insufficient this function emits an exception.
+    ///
+    /// @warning this function does not check if options_ storage pointer
+    /// is intitialized but this check is not needed here because it is done
+    /// in the \ref build function.
+    ///
+    /// @throw Dhcp4ConfigError if parameters provided in the configuration
+    /// are invalid.
+    void createOption() {
+        // Option code is held in the uint32_t storage but is supposed to
+        // be uint16_t value. We need to check that value in the configuration
+        // does not exceed range of uint16_t and is not zero.
+        uint32_t option_code = getUint32Param("code");
+        if (option_code == 0) {
+            isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+                      << " be equal to zero. Option code '0' is reserved in"
+                      << " DHCPv4.");
+        } else if (option_code > std::numeric_limits<uint16_t>::max()) {
+            isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+                      << " exceed " << std::numeric_limits<uint16_t>::max());
+        }
+        // Check that the option name has been specified, is non-empty and does not
+        // contain spaces.
+        // @todo possibly some more restrictions apply here?
+        std::string option_name = getStringParam("name");
+        if (option_name.empty()) {
+            isc_throw(Dhcp4ConfigError, "Parser error: option name must not be"
+                      << " empty");
+        } else if (option_name.find(" ") != std::string::npos) {
+            isc_throw(Dhcp4ConfigError, "Parser error: option name must not contain"
+                      << " spaces");
+        }
+
+        // Get option data from the configuration database ('data' field).
+        const std::string option_data = getStringParam("data");
+        const bool csv_format = getBooleanParam("csv-format");
+        // Transform string of hexadecimal digits into binary format.
+        std::vector<uint8_t> binary;
+        std::vector<std::string> data_tokens;
+
+        if (csv_format) {
+            // If the option data is specified as a string of comma
+            // separated values then we need to split this string into
+            // individual values - each value will be used to initialize
+            // one data field of an option.
+            data_tokens = isc::util::str::tokens(option_data, ",");
+        } else {
+            // Otherwise, the option data is specified as a string of
+            // hexadecimal digits that we have to turn into binary format.
+            try {
+                util::encode::decodeHex(option_data, binary);
+            } catch (...) {
+                isc_throw(Dhcp4ConfigError, "Parser error: option data is not a valid"
+                          << " string of hexadecimal digits: " << option_data);
+            }
+        }
+        // Get all existing DHCPv4 option definitions. The one that matches
+        // our option will be picked and used to create it.
+        OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V4);
+        // Get search index #1. It allows searching for options definitions
+        // using option type value.
+        const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+        // Get all option definitions matching option code we want to create.
+        const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+        size_t num_defs = std::distance(range.first, range.second);
+        OptionPtr option;
+        // Currently we do not allow duplicated definitions and if there are
+        // any duplicates we issue internal server error.
+        if (num_defs > 1) {
+            isc_throw(Dhcp4ConfigError, "Internal error: currently it is not"
+                      << " supported to initialize multiple option definitions"
+                      << " for the same option code. This will be supported once"
+                      << " there option spaces are implemented.");
+        } else if (num_defs == 0) {
+            if (csv_format) {
+                isc_throw(Dhcp4ConfigError, "the CSV option data format can be"
+                          " used to specify values for an option that has a"
+                          " definition. The option with code " << option_code
+                          << " does not have a definition.");
+            }
+
+            // @todo We have a limited set of option definitions intiialized at the moment.
+            // In the future we want to initialize option definitions for all options.
+            // Consequently an error will be issued if an option definition does not exist
+            // for a particular option code. For now it is ok to create generic option
+            // if definition does not exist.
+            OptionPtr option(new Option(Option::V4, static_cast<uint16_t>(option_code),
+                                        binary));
+            // The created option is stored in option_descriptor_ class member until the
+            // commit stage when it is inserted into the main storage. If an option with the
+            // same code exists in main storage already the old option is replaced.
+            option_descriptor_.option = option;
+            option_descriptor_.persistent = false;
+        } else {
+            // We have exactly one option definition for the particular option code
+            // use it to create the option instance.
+            const OptionDefinitionPtr& def = *(range.first);
+            try {
+                OptionPtr option = csv_format ?
+                    def->optionFactory(Option::V4, option_code, data_tokens) :
+                    def->optionFactory(Option::V4, option_code, binary);
+                Subnet::OptionDescriptor desc(option, false);
+                option_descriptor_.option = option;
+                option_descriptor_.persistent = false;
+            } catch (const isc::Exception& ex) {
+                isc_throw(Dhcp4ConfigError, "Parser error: option data does not match"
+                          << " option definition (code " << option_code << "): "
+                          << ex.what());
+            }
+        }
+    }
+
+    /// @brief Get a parameter from the strings storage.
+    ///
+    /// @param param_id parameter identifier.
+    /// @throw Dhcp4ConfigError if parameter has not been found.
+    std::string getStringParam(const std::string& param_id) const {
+        StringStorage::const_iterator param = string_values_.find(param_id);
+        if (param == string_values_.end()) {
+            isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
+                      << " '" << param_id << "' not specified");
+        }
+        return (param->second);
+    }
+
+    /// @brief Get a parameter from the uint32 values storage.
+    ///
+    /// @param param_id parameter identifier.
+    /// @throw Dhcp4ConfigError if parameter has not been found.
+    uint32_t getUint32Param(const std::string& param_id) const {
+        Uint32Storage::const_iterator param = uint32_values_.find(param_id);
+        if (param == uint32_values_.end()) {
+            isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
+                      << " '" << param_id << "' not specified");
+        }
+        return (param->second);
+    }
+
+    /// @brief Get a parameter from the boolean values storage.
+    ///
+    /// @param param_id parameter identifier.
+    ///
+    /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
+    /// @return a value of the boolean parameter.
+    bool getBooleanParam(const std::string& param_id) const {
+        BooleanStorage::const_iterator param = boolean_values_.find(param_id);
+        if (param == boolean_values_.end()) {
+            isc_throw(isc::dhcp::Dhcp4ConfigError, "parser error: option-data parameter"
+                      << " '" << param_id << "' not specified");
+        }
+        return (param->second);
+    }
+
+    /// Storage for uint32 values (e.g. option code).
+    Uint32Storage uint32_values_;
+    /// Storage for string values (e.g. option name or data).
+    StringStorage string_values_;
+    /// Storage for boolean values.
+    BooleanStorage boolean_values_;
+    /// Pointer to options storage. This storage is provided by
+    /// the calling class and is shared by all OptionDataParser objects.
+    OptionStorage* options_;
+    /// Option descriptor holds newly configured option.
+    Subnet::OptionDescriptor option_descriptor_;
+};
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Unless otherwise specified, parsed options will be stored in
+    /// a global option container (option_default). That storage location
+    /// is overriden on a subnet basis.
+    OptionDataListParser(const std::string&)
+        : options_(&option_defaults), local_options_() { }
+
+    /// @brief Parses entries that define options' data for a subnet.
+    ///
+    /// This method iterates over all entries that define option data
+    /// for options within a single subnet and creates options' instances.
+    ///
+    /// @param option_data_list pointer to a list of options' data sets.
+    /// @throw Dhcp4ConfigError if option parsing failed.
+    void build(ConstElementPtr option_data_list) {
+        BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+            boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
+            // options_ member will hold instances of all options thus
+            // each OptionDataParser takes it as a storage.
+            parser->setStorage(&local_options_);
+            // Build the instance of a single option.
+            parser->build(option_value);
+            // Store a parser as it will be used to commit.
+            parsers_.push_back(parser);
+        }
+    }
+
+    /// @brief Set storage for option instances.
+    ///
+    /// @param storage pointer to options storage.
+    void setStorage(OptionStorage* storage) {
+        options_ = storage;
+    }
+
+
+    /// @brief Commit all option values.
+    ///
+    /// This function invokes commit for all option values.
+    void commit() {
+        BOOST_FOREACH(ParserPtr parser, parsers_) {
+            parser->commit();
+        }
+        // Parsing was successful and we have all configured
+        // options in local storage. We can now replace old values
+        // with new values.
+        std::swap(local_options_, *options_);
+    }
+
+    /// @brief Create DhcpDataListParser object
+    ///
+    /// @param param_name param name.
+    ///
+    /// @return DhcpConfigParser object.
+    static DhcpConfigParser* factory(const std::string& param_name) {
+        return (new OptionDataListParser(param_name));
+    }
+
+    /// Intermediate option storage. This storage is used by
+    /// lower level parsers to add new options.  Values held
+    /// in this storage are assigned to main storage (options_)
+    /// if overall parsing was successful.
+    OptionStorage local_options_;
+    /// Pointer to options instances storage.
+    OptionStorage* options_;
+    /// Collection of parsers;
+    ParserCollection parsers_;
 };
 
 /// @brief this class parses a single subnet
 ///
 /// This class parses the whole subnet definition. It creates parsers
 /// for received configuration parameters as needed.
-class Subnet4ConfigParser : public Dhcp4ConfigParser {
+class Subnet4ConfigParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor
@@ -470,36 +975,45 @@ public:
     void build(ConstElementPtr subnet) {
 
         BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
-
             ParserPtr parser(createSubnet4ConfigParser(param.first));
-
-            // if this is an Uint32 parser, tell it to store the values
-            // in values_, rather than in global storage
-            boost::shared_ptr<Uint32Parser> uint_parser =
-                boost::dynamic_pointer_cast<Uint32Parser>(parser);
-            if (uint_parser) {
-                uint_parser->setStorage(&uint32_values_);
-            } else {
-
-                boost::shared_ptr<StringParser> string_parser =
-                    boost::dynamic_pointer_cast<StringParser>(parser);
-                if (string_parser) {
-                    string_parser->setStorage(&string_values_);
-                } else {
-
-                    boost::shared_ptr<PoolParser> pool_parser =
-                        boost::dynamic_pointer_cast<PoolParser>(parser);
-                    if (pool_parser) {
-                        pool_parser->setStorage(&pools_);
-                    }
-                }
+            // The actual type of the parser is unknown here. We have to discover
+            // the parser type here to invoke the corresponding setStorage function
+            // on it.  We discover parser type by trying to cast the parser to various
+            // parser types and checking which one was successful. For this one
+            // a setStorage and build methods are invoked.
+
+            // Try uint32 type parser.
+            if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
+                                                          param.second) &&
+                // Try string type parser.
+                !buildParser<StringParser, StringStorage >(parser, string_values_,
+                                                           param.second) &&
+                // Try pool parser.
+                !buildParser<PoolParser, PoolStorage >(parser, pools_,
+                                                       param.second) &&
+                // Try option data parser.
+                !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
+                                                                   param.second)) {
+                // Appropriate parsers are created in the createSubnet6ConfigParser
+                // and they should be limited to those that we check here for. Thus,
+                // if we fail to find a matching parser here it is a programming error.
+                isc_throw(Dhcp4ConfigError, "failed to find suitable parser");
             }
-
-            parser->build(param.second);
-            parsers_.push_back(parser);
+        }
+        // In order to create new subnet we need to get the data out
+        // of the child parsers first. The only way to do it is to
+        // invoke commit on them because it will make them write
+        // parsed data into storages we have supplied.
+        // Note that triggering commits on child parsers does not
+        // affect global data because we supplied pointers to storages
+        // local to this object. Thus, even if this method fails
+        // later on, the configuration remains consistent.
+        BOOST_FOREACH(ParserPtr parser, parsers_) {
+            parser->commit();
         }
 
-        // Ok, we now have subnet parsed
+        // Create a subnet.
+        createSubnet();
     }
 
     /// @brief commits received configuration.
@@ -510,24 +1024,82 @@ public:
     /// objects. Subnet4 are then added to DHCP CfgMgr.
     /// @throw Dhcp4ConfigError if there are any issues encountered during commit
     void commit() {
+        if (subnet_) {
+            CfgMgr::instance().addSubnet4(subnet_);
+        }
+    }
+
+private:
 
+    /// @brief Set storage for a parser and invoke build.
+    ///
+    /// This helper method casts the provided parser pointer to the specified
+    /// type. If the cast is successful it sets the corresponding storage for
+    /// this parser, invokes build on it and saves the parser.
+    ///
+    /// @tparam T parser type to which parser argument should be cast.
+    /// @tparam Y storage type for the specified parser type.
+    /// @param parser parser on which build must be invoked.
+    /// @param storage reference to a storage that will be set for a parser.
+    /// @param subnet subnet element read from the configuration and being parsed.
+    /// @return true if parser pointer was successfully cast to specialized
+    /// parser type provided as Y.
+    template<typename T, typename Y>
+    bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+        // We need to cast to T in order to set storage for the parser.
+        boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+        // It is common that this cast is not successful because we try to cast to all
+        // supported parser types as we don't know the type of a parser in advance.
+        if (cast_parser) {
+            // Cast, successful so we go ahead with setting storage and actual parse.
+            cast_parser->setStorage(&storage);
+            parser->build(subnet);
+            parsers_.push_back(parser);
+            // We indicate that cast was successful so as the calling function
+            // may skip attempts to cast to other parser types and proceed to
+            // next element.
+            return (true);
+        }
+        // It was not successful. Indicate that another parser type
+        // should be tried.
+        return (false);
+    }
+
+    /// @brief Create a new subnet using a data from child parsers.
+    ///
+    /// @throw isc::dhcp::Dhcp4ConfigError if subnet configuration parsing failed.
+    void createSubnet() {
         StringStorage::const_iterator it = string_values_.find("subnet");
         if (it == string_values_.end()) {
             isc_throw(Dhcp4ConfigError,
                       "Mandatory subnet definition in subnet missing");
         }
+        // Remove any spaces or tabs.
         string subnet_txt = it->second;
         boost::erase_all(subnet_txt, " ");
         boost::erase_all(subnet_txt, "\t");
 
+        // The subnet format is prefix/len. We are going to extract
+        // the prefix portion of a subnet string to create IOAddress
+        // object from it. IOAddress will be passed to the Subnet's
+        // constructor later on. In order to extract the prefix we
+        // need to get all characters preceding "/".
         size_t pos = subnet_txt.find("/");
         if (pos == string::npos) {
             isc_throw(Dhcp4ConfigError,
                       "Invalid subnet syntax (prefix/len expected):" << it->second);
         }
+
+        // Try to create the address object. It also validates that
+        // the address syntax is ok.
         IOAddress addr(subnet_txt.substr(0, pos));
         uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
 
+        // Get all 'time' parameters using inheritance.
+        // If the subnet-specific value is defined then use it, else
+        // use the global value. The global value must always be
+        // present. If it is not, it is an internal error and exception
+        // is thrown.
         Triplet<uint32_t> t1 = getParam("renew-timer");
         Triplet<uint32_t> t2 = getParam("rebind-timer");
         Triplet<uint32_t> valid = getParam("valid-lifetime");
@@ -539,16 +1111,48 @@ public:
 
         LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
 
-        Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid));
+        subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
 
         for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
-            subnet->addPool4(*it);
+            subnet_->addPool4(*it);
         }
 
-        CfgMgr::instance().addSubnet4(subnet);
-    }
+        Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
+        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
-private:
+        // Add subnet specific options.
+        BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
+            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
+            if (std::distance(range.first, range.second) > 0) {
+                LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+                    .arg(desc.option->getType()).arg(addr.toText());
+            }
+            subnet_->addOption(desc.option, false, "dhcp4");
+        }
+
+        // Check all global options and add them to the subnet object if
+        // they have been configured in the global scope. If they have been
+        // configured in the subnet scope we don't add global option because
+        // the one configured in the subnet scope always takes precedence.
+        BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
+            // Get all options specified locally in the subnet and having
+            // code equal to global option's code.
+            Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp4");
+            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+            Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
+            // @todo: In the future we will be searching for options using either
+            // an option code or namespace. Currently we have only the option
+            // code available so if there is at least one option found with the
+            // specific code we don't add the globally configured option.
+            // @todo with this code the first globally configured option
+            // with the given code will be added to a subnet. We may
+            // want to issue a warning about dropping the configuration of
+            // a global option if one already exsists.
+            if (std::distance(range.first, range.second) == 0) {
+                subnet_->addOption(desc.option, false, "dhcp4");
+            }
+        }
+    }
 
     /// @brief creates parsers for entries in subnet definition
     ///
@@ -557,14 +1161,15 @@ private:
     /// @param config_id name od the entry
     /// @return parser object for specified entry name
     /// @throw NotImplemented if trying to create a parser for unknown config element
-    Dhcp4ConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
+    DhcpConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
         FactoryMap factories;
 
-        factories["valid-lifetime"] = Uint32Parser::Factory;
-        factories["renew-timer"] = Uint32Parser::Factory;
-        factories["rebind-timer"] = Uint32Parser::Factory;
-        factories["subnet"] = StringParser::Factory;
-        factories["pool"] = PoolParser::Factory;
+        factories["valid-lifetime"] = Uint32Parser::factory;
+        factories["renew-timer"] = Uint32Parser::factory;
+        factories["rebind-timer"] = Uint32Parser::factory;
+        factories["subnet"] = StringParser::factory;
+        factories["pool"] = PoolParser::factory;
+        factories["option-data"] = OptionDataListParser::factory;
 
         FactoryMap::iterator f = factories.find(config_id);
         if (f == factories.end()) {
@@ -620,8 +1225,14 @@ private:
     /// storage for pools belonging to this subnet
     PoolStorage pools_;
 
+    /// storage for options belonging to this subnet
+    OptionStorage options_;
+
     /// parsers are stored here
     ParserCollection parsers_;
+
+    /// @brief Pointer to the created subnet object.
+    isc::dhcp::Subnet4Ptr subnet_;
 };
 
 /// @brief this class parses list of subnets
@@ -629,7 +1240,7 @@ private:
 /// This is a wrapper parser that handles the whole list of Subnet4
 /// definitions. It iterates over all entries and creates Subnet4ConfigParser
 /// for each entry.
-class Subnets4ListConfigParser : public Dhcp4ConfigParser {
+class Subnets4ListConfigParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor
@@ -650,7 +1261,6 @@ public:
         // used: Subnet4ConfigParser
 
         BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
-
             ParserPtr parser(new Subnet4ConfigParser("subnet"));
             parser->build(subnet);
             subnets_.push_back(parser);
@@ -678,14 +1288,20 @@ public:
     /// @brief Returns Subnet4ListConfigParser object
     /// @param param_name name of the parameter
     /// @return Subnets4ListConfigParser object
-    static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new Subnets4ListConfigParser(param_name));
     }
 
     /// @brief collection of subnet parsers.
     ParserCollection subnets_;
+
 };
 
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
 /// @brief creates global parsers
 ///
 /// This method creates global parsers that parse global parameters, i.e.
@@ -694,15 +1310,16 @@ public:
 /// @param config_id pointer to received global configuration entry
 /// @return parser for specified global DHCPv4 parameter
 /// @throw NotImplemented if trying to create a parser for unknown config element
-Dhcp4ConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
+DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
     FactoryMap factories;
 
-    factories["valid-lifetime"] = Uint32Parser::Factory;
-    factories["renew-timer"] = Uint32Parser::Factory;
-    factories["rebind-timer"] = Uint32Parser::Factory;
-    factories["interface"] = InterfaceListConfigParser::Factory;
-    factories["subnet4"] = Subnets4ListConfigParser::Factory;
-    factories["version"] = StringParser::Factory;
+    factories["valid-lifetime"] = Uint32Parser::factory;
+    factories["renew-timer"] = Uint32Parser::factory;
+    factories["rebind-timer"] = Uint32Parser::factory;
+    factories["interface"] = InterfaceListConfigParser::factory;
+    factories["subnet4"] = Subnets4ListConfigParser::factory;
+    factories["option-data"] = OptionDataListParser::factory;
+    factories["version"] = StringParser::factory;
 
     FactoryMap::iterator f = factories.find(config_id);
     if (f == factories.end()) {
@@ -729,44 +1346,129 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
 
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
 
-    ParserCollection parsers;
+    // Some of the values specified in the configuration depend on
+    // other values. Typically, the values in the subnet6 structure
+    // depend on the global values. Thus we need to make sure that
+    // the global values are processed first and that they can be
+    // accessed by the subnet6 parsers. We separate parsers that
+    // should process data first (independent_parsers) from those
+    // that must process data when the independent data is already
+    // processed (dependent_parsers).
+    ParserCollection independent_parsers;
+    ParserCollection dependent_parsers;
+
+    // The subnet parsers implement data inheritance by directly
+    // accessing global storage. For this reason the global data
+    // parsers must store the parsed data into global storages
+    // immediately. This may cause data inconsistency if the
+    // parsing operation fails after the global storage has been
+    // modified. We need to preserve the original global data here
+    // so as we can rollback changes when an error occurs.
+    Uint32Storage uint32_local(uint32_defaults);
+    StringStorage string_local(string_defaults);
+    OptionStorage option_local(option_defaults);
+
+    // answer will hold the result.
+    ConstElementPtr answer;
+    // rollback informs whether error occured and original data
+    // have to be restored to global storages.
+    bool rollback = false;
+    string current_parser;  // For error messages
     try {
+
+        // Iterate over all independent parsers first (all but subnet4)
+        // and try to parse the data.
         BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+            if (config_pair.first != "subnet4") {
+                current_parser = config_pair.first;
+                ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+                LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED1)
+                          .arg(current_parser);
+                independent_parsers.push_back(parser);
+                parser->build(config_pair.second);
+                // The commit operation here may modify the global storage
+                // but we need it so as the subnet6 parser can access the
+                // parsed data.
+                parser->commit();
+            }
+        }
 
-            ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
-            parser->build(config_pair.second);
-            parsers.push_back(parser);
+        // Process dependent configuration data.
+        BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+            if (config_pair.first == "subnet4") {
+                current_parser = config_pair.first;
+                ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+                LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED2)
+                          .arg(current_parser);
+                dependent_parsers.push_back(parser);
+                parser->build(config_pair.second);
+            }
         }
+
     } catch (const isc::Exception& ex) {
-        ConstElementPtr answer = isc::config::createAnswer(1,
-                                 string("Configuration parsing failed:") + ex.what());
-        return (answer);
+        LOG_ERROR(dhcp4_logger, DHCP4_PARSER_CREATE_FAIL).arg(current_parser)
+                  .arg(ex.what());
+        answer =
+            isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+
+        // An error occured, so make sure that we restore original data.
+        rollback = true;
+
     } catch (...) {
         // for things like bad_cast in boost::lexical_cast
-        ConstElementPtr answer = isc::config::createAnswer(1,
-                                 string("Configuration parsing failed"));
+        LOG_ERROR(dhcp4_logger, DHCP4_PARSER_CREATE_EXCEPTION)
+                  .arg(current_parser);
+        answer =
+            isc::config::createAnswer(1, string("Configuration parsing failed"));
+
+        // An error occured, so make sure that we restore original data.
+        rollback = true;
     }
 
-    try {
-        BOOST_FOREACH(ParserPtr parser, parsers) {
-            parser->commit();
+    // So far so good, there was no parsing error so let's commit the
+    // configuration. This will add created subnets and option values into
+    // the server's configuration.
+    // This operation should be exception safe but let's make sure.
+    if (!rollback) {
+        try {
+            BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
+                parser->commit();
+            }
+        }
+        catch (const isc::Exception& ex) {
+            LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
+            answer =
+                isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+            rollback = true;
+
+        } catch (...) {
+            // for things like bad_cast in boost::lexical_cast
+            LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
+            answer =
+                isc::config::createAnswer(2, string("Configuration commit failed"));
+            rollback = true;
+
         }
     }
-    catch (const isc::Exception& ex) {
-        ConstElementPtr answer = isc::config::createAnswer(2,
-                                 string("Configuration commit failed:") + ex.what());
+
+    // Rollback changes as the configuration parsing failed.
+    if (rollback) {
+        std::swap(uint32_defaults, uint32_local);
+        std::swap(string_defaults, string_local);
+        std::swap(option_defaults, option_local);
         return (answer);
-    } catch (...) {
-        // for things like bad_cast in boost::lexical_cast
-        ConstElementPtr answer = isc::config::createAnswer(2,
-                                 string("Configuration commit failed"));
     }
 
     LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
 
-    ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+    // Everything was fine. Configuration is successful.
+    answer = isc::config::createAnswer(0, "Configuration commited.");
     return (answer);
 }
 
+const std::map<std::string, uint32_t>& getUint32Defaults() {
+    return (uint32_defaults);
+}
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index 2ee9cf6..3247241 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -14,6 +14,7 @@
 
 #include <exceptions/exceptions.h>
 #include <cc/data.h>
+#include <stdint.h>
 #include <string>
 
 #ifndef DHCP4_CONFIG_PARSER_H
@@ -27,14 +28,8 @@ namespace dhcp {
 
 class Dhcpv4Srv;
 
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
-typedef std::map<std::string, uint32_t> Uint32Storage;
-
-/// @brief a collection of elements that store string values
-typedef std::map<std::string, std::string> StringStorage;
-
 /// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv4Srv object.
+/// @c Dhcpv4Srv object.
 class Dhcp4ConfigError : public isc::Exception {
 public:
 
@@ -47,97 +42,12 @@ public:
         : isc::Exception(file, line, what) {}
 };
 
-/// @brief Base abstract class for all DHCPv4 parsers
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
 ///
-/// Each instance of a class derived from this class parses one specific config
-/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
-/// complex (e.g. a subnet). In such case, it is likely that a parser will
-/// spawn child parsers to parse child elements in the configuration.
-/// @todo: Merge this class with DhcpConfigParser in src/bin/dhcp6
-class Dhcp4ConfigParser {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private to make it explicit that this is a
-    /// pure base class.
-    //@{
-private:
-
-    // Private construtor and assignment operator assures that nobody
-    // will be able to copy or assign a parser. There are no defined
-    // bodies for them.
-    Dhcp4ConfigParser(const Dhcp4ConfigParser& source);
-    Dhcp4ConfigParser& operator=(const Dhcp4ConfigParser& source);
-protected:
-    /// \brief The default constructor.
-    ///
-    /// This is intentionally defined as \c protected as this base class should
-    /// never be instantiated (except as part of a derived class).
-    Dhcp4ConfigParser() {}
-public:
-    /// The destructor.
-    virtual ~Dhcp4ConfigParser() {}
-    //@}
-
-    /// \brief Prepare configuration value.
-    ///
-    /// This method parses the "value part" of the configuration identifier
-    /// that corresponds to this derived class and prepares a new value to
-    /// apply to the server.
-    ///
-    /// This method must validate the given value both in terms of syntax
-    /// and semantics of the configuration, so that the server will be
-    /// validly configured at the time of \c commit().  Note: the given
-    /// configuration value is normally syntactically validated, but the
-    /// \c build() implementation must also expect invalid input.  If it
-    /// detects an error it may throw an exception of a derived class
-    /// of \c isc::Exception.
-    ///
-    /// Preparing a configuration value will often require resource
-    /// allocation.  If it fails, it may throw a corresponding standard
-    /// exception.
-    ///
-    /// This method is not expected to be called more than once in the
-    /// life of the object. Although multiple calls are not prohibited
-    /// by the interface, the behavior is undefined.
-    ///
-    /// \param config_value The configuration value for the identifier
-    /// corresponding to the derived class.
-    virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
-    /// \brief Apply the prepared configuration value to the server.
-    ///
-    /// This method is expected to be exception free, and, as a consequence,
-    /// it should normally not involve resource allocation.
-    /// Typically it would simply perform exception free assignment or swap
-    /// operation on the value prepared in \c build().
-    /// In some cases, however, it may be very difficult to meet this
-    /// condition in a realistic way, while the failure case should really
-    /// be very rare.  In such a case it may throw, and, if the parser is
-    /// called via \c configureDhcp4Server(), the caller will convert the
-    /// exception as a fatal error.
-    ///
-    /// This method is expected to be called after \c build(), and only once.
-    /// The result is undefined otherwise.
-    virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<Dhcp4ConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure DHCPv4 server (\c Dhcpv4Srv) with a set of configuration values.
-///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
+/// This function parses configuration information stored in @c config_set
+/// and configures the @c server by applying the configuration to it.
 /// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
+/// derived class implementations of @c DhcpConfigParser meet the assumption,
 /// that is, it ensures that either configuration is fully applied or the
 /// state of the server is intact.
 ///
@@ -153,7 +63,8 @@ typedef std::vector<ParserPtr> ParserCollection;
 /// reconfiguration statuses. It may return the following response codes:
 /// 0 - configuration successful
 /// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
+/// 2 - commit failed (parsing was successful, but failed to store the
+/// values in to server's configuration)
 ///
 /// @param config_set a new configuration (JSON) for DHCPv4 server
 /// @return answer that contains result of reconfiguration
@@ -161,6 +72,16 @@ isc::data::ConstElementPtr
 configureDhcp4Server(Dhcpv4Srv&,
                      isc::data::ConstElementPtr config_set);
 
+
+/// @brief Returns the global uint32_t values storage.
+///
+/// This function must be only used by unit tests that need
+/// to access uint32_t global storage to verify that the
+/// Uint32Parser works as expected.
+///
+/// @return a reference to a global uint32 values storage.
+const std::map<std::string, uint32_t>& getUint32Defaults();
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index d5066e8..13acf83 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -34,6 +34,42 @@
         "item_default": 4000
       },
 
+      { "item_name": "option-data",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [],
+        "list_item_spec":
+        {
+          "item_name": "single-option-data",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "map_item_spec": [
+          {
+            "item_name": "name",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          },
+
+          { "item_name": "code",
+            "item_type": "integer",
+            "item_optional": false,
+            "item_default": 0
+          },
+          { "item_name": "data",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          },
+          { "item_name": "csv-format",
+            "item_type": "boolean",
+            "item_optional": false,
+            "item_default": False
+          } ]
+        }
+      },
+
       { "item_name": "subnet4",
         "item_type": "list",
         "item_optional": false,
@@ -80,9 +116,45 @@
                         "item_optional": false,
                         "item_default": ""
                     }
-                }
-            ]
-        }
+                },
+
+                { "item_name": "option-data",
+                  "item_type": "list",
+                  "item_optional": false,
+                  "item_default": [],
+                  "list_item_spec":
+                  {
+                    "item_name": "single-option-data",
+                    "item_type": "map",
+                    "item_optional": false,
+                    "item_default": {},
+                    "map_item_spec": [
+                    {
+                      "item_name": "name",
+                      "item_type": "string",
+                      "item_optional": false,
+                      "item_default": ""
+                    },
+                    {
+                      "item_name": "code",
+                      "item_type": "integer",
+                      "item_optional": false,
+                      "item_default": 0
+                    },
+                    {
+                      "item_name": "data",
+                      "item_type": "string",
+                      "item_optional": false,
+                      "item_default": ""
+                    },
+                    { "item_name": "csv-format",
+                      "item_type": "boolean",
+                      "item_optional": false,
+                      "item_default": False
+                    } ]
+                  }
+                } ]
+         }
       }
     ],
     "commands": [
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 994f004..8c30534 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -26,29 +26,34 @@ to establish a session with the BIND 10 control channel.
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the IPv4 DHCP server.
 
+% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. it is output during server startup, and when an updated
+configuration is committed by the administrator.  Additional information
+may be provided.
+
 % DHCP4_CONFIG_LOAD_FAIL failed to load configuration: %1
 This critical error message indicates that the initial DHCPv4
 configuration has failed. The server will start, but nothing will be
 served until the configuration has been corrected.
 
-% DHCP4_CONFIG_UPDATE updated configuration received: %1
-A debug message indicating that the IPv4 DHCP server has received an
-updated configuration from the BIND 10 configuration system.
+% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv4 subnet.
 
 % DHCP4_CONFIG_START DHCPv4 server is processing the following configuration: %1
 This is a debug message that is issued every time the server receives a
 configuration. That happens at start up and also when a server configuration
 change is committed by the administrator.
 
-% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
-This is an informational message reporting that the configuration has
-been extended to include the specified IPv4 subnet.
+% DHCP4_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the IPv4 DHCP server has received an
+updated configuration from the BIND 10 configuration system.
 
-% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
-This is an informational message announcing the successful processing of a
-new configuration. it is output during server startup, and when an updated
-configuration is committed by the administrator.  Additional information
-may be provided.
+% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
+This warning message is issued on an attempt to configure multiple options with the
+same option code for the particular subnet. Adding multiple options is uncommon
+for DHCPv4, but it is not prohibited.
 
 % DHCP4_NOT_RUNNING IPv4 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
@@ -70,7 +75,7 @@ may well be a valid DHCP packet, just a type not expected by the server
 
 % DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
 The IPv4 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
 the message.
 
 % DHCP4_PACKET_SEND_FAIL failed to send DHCPv4 packet: %1
@@ -83,6 +88,41 @@ This error is output if the server failed to assemble the data to be
 returned to the client into a valid packet.  The cause is most likely
 to be a programming error: please raise a bug report.
 
+% DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
+On receipt of message containing details to a change of the IPv4 DHCP
+server configuration, a set of parsers were successfully created, but one
+of them failed to commit its changes due to a low-level system exception
+being raised.  Additional messages may be output indicating the reason.
+
+% DHCP4_PARSER_COMMIT_FAIL parser failed to commit changes: %1
+On receipt of message containing details to a change of the IPv4 DHCP
+server configuration, a set of parsers were successfully created, but
+one of them failed to commit its changes.  The reason for the failure
+is given in the message.
+
+% DHCP4_PARSER_CREATED1 created parser for configuration element %1
+A debug message output during a configuration update of the IPv4 DHCP
+server, notifying that the parser for the specified configuration element
+has been successfully created in the first pass through creating parsers
+
+% DHCP4_PARSER_CREATED2 created parser for configuration element %1
+A debug message output during a configuration update of the IPv4 DHCP
+server, notifying that the parser for the specified configuration element
+has been successfully created in the second pass through creating parsers
+
+% DHCP4_PARSER_CREATE_FAIL failed to create parser for configuration element %1: %2
+On receipt of message containing details to a change of its configuration,
+the IPv4 DHCP server failed to create a parser to decode the contents of the
+named configuration element.  The reason for the failure is given in the
+message.
+
+% DHCP4_PARSER_CREATE_EXCEPTION failed to create parser for configuration element %1
+On receipt of message containing details to a change of its configuration,
+the IPv4 DHCP server failed to create a parser to decode the contents
+of the named configuration element.  The message has been output in
+response to a non-BIND 10 exception being raised.  Additional messages
+may give further information.
+
 % DHCP4_QUERY_DATA received packet type %1, data is <%2>
 A debug message listing the data received from the client.
 
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index e601919..c0ebcb9 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -66,13 +66,13 @@ dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp4_unittests_LDADD = $(GTEST_LDADD)
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 endif
 
 noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index a4ccabd..c6af514 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -17,11 +17,13 @@
 #include <arpa/inet.h>
 #include <gtest/gtest.h>
 
+#include <config/ccsession.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/config_parser.h>
-#include <config/ccsession.h>
+#include <dhcp/option4_addrlst.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <boost/foreach.hpp>
 #include <iostream>
 #include <fstream>
 #include <sstream>
@@ -34,12 +36,6 @@ using namespace isc::asiolink;
 using namespace isc::data;
 using namespace isc::config;
 
-namespace isc {
-namespace dhcp {
-extern Uint32Storage uint32_defaults;
-}
-}
-
 namespace {
 
 class Dhcp4ParserTest : public ::testing::Test {
@@ -54,7 +50,9 @@ public:
 
     // Checks if global parameter of name have expected_value
     void checkGlobalUint32(string name, uint32_t expected_value) {
-        Uint32Storage::const_iterator it = uint32_defaults.find(name);
+        const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
+        std::map<std::string, uint32_t>::const_iterator it =
+            uint32_defaults.find(name);
         if (it == uint32_defaults.end()) {
             ADD_FAILURE() << "Expected uint32 with name " << name
                           << " not found";
@@ -73,9 +71,199 @@ public:
     }
 
     ~Dhcp4ParserTest() {
+        resetConfiguration();
         delete srv_;
     };
 
+    /// @brief Create the simple configuration with single option.
+    ///
+    /// This function allows to set one of the parameters that configure
+    /// option value. These parameters are: "name", "code", "data" and
+    /// "csv-format".
+    ///
+    /// @param param_value string holiding option parameter value to be
+    /// injected into the configuration string.
+    /// @param parameter name of the parameter to be configured with
+    /// param value.
+    /// @return configuration string containing custom values of parameters
+    /// describing an option.
+    std::string createConfigWithOption(const std::string& param_value,
+                                       const std::string& parameter) {
+        std::map<std::string, std::string> params;
+        if (parameter == "name") {
+            params["name"] = param_value;
+            params["code"] = "56";
+            params["data"] = "AB CDEF0105";
+            params["csv-format"] = "False";
+        } else if (parameter == "code") {
+            params["name"] = "option_foo";
+            params["code"] = param_value;
+            params["data"] = "AB CDEF0105";
+            params["csv-format"] = "False";
+        } else if (parameter == "data") {
+            params["name"] = "option_foo";
+            params["code"] = "56";
+            params["data"] = param_value;
+            params["csv-format"] = "False";
+        } else if (parameter == "csv-format") {
+            params["name"] = "option_foo";
+            params["code"] = "56";
+            params["data"] = "AB CDEF0105";
+            params["csv-format"] = param_value;
+        }
+        return (createConfigWithOption(params));
+    }
+
+    /// @brief Create simple configuration with single option.
+    ///
+    /// This function creates a configuration for a single option with
+    /// custom values for all parameters that describe the option.
+    ///
+    /// @params params map holding parameters and their values.
+    /// @return configuration string containing custom values of parameters
+    /// describing an option.
+    std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
+        std::ostringstream stream;
+        stream << "{ \"interface\": [ \"all\" ],"
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"subnet4\": [ { "
+            "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+            "    \"subnet\": \"192.0.2.0/24\", "
+            "    \"option-data\": [ {";
+        bool first = true;
+        typedef std::pair<std::string, std::string> ParamPair;
+        BOOST_FOREACH(ParamPair param, params) {
+            if (!first) {
+                stream << ", ";
+            } else {
+                // cppcheck-suppress unreadVariable
+                first = false;
+            }
+            if (param.first == "name") {
+                stream << "\"name\": \"" << param.second << "\"";
+            } else if (param.first == "code") {
+                stream << "\"code\": " << param.second << "";
+            } else if (param.first == "data") {
+                stream << "\"data\": \"" << param.second << "\"";
+            } else if (param.first == "csv-format") {
+                stream << "\"csv-format\": " << param.second;
+            }
+        }
+        stream <<
+            "        } ]"
+            " } ],"
+            "\"valid-lifetime\": 4000 }";
+        return (stream.str());
+    }
+
+    /// @brief Test invalid option parameter value.
+    ///
+    /// This test function constructs the simple configuration
+    /// string and injects invalid option configuration into it.
+    /// It expects that parser will fail with provided option code.
+    ///
+    /// @param param_value string holding invalid option parameter value
+    /// to be injected into configuration string.
+    /// @param parameter name of the parameter to be configured with
+    /// param_value (can be any of "name", "code", "data")
+    void testInvalidOptionParam(const std::string& param_value,
+                                const std::string& parameter) {
+        ConstElementPtr x;
+        std::string config = createConfigWithOption(param_value, parameter);
+        ElementPtr json = Element::fromJSON(config);
+        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+        ASSERT_TRUE(x);
+        comment_ = parseAnswer(rcode_, x);
+        ASSERT_EQ(1, rcode_);
+    }
+
+    /// @brief Test option against given code and data.
+    ///
+    /// @param option_desc option descriptor that carries the option to
+    /// be tested.
+    /// @param expected_code expected code of the option.
+    /// @param expected_data expected data in the option.
+    /// @param expected_data_len length of the reference data.
+    /// @param extra_data if true extra data is allowed in an option
+    /// after tested data.
+    void testOption(const Subnet::OptionDescriptor& option_desc,
+                    uint16_t expected_code, const uint8_t* expected_data,
+                    size_t expected_data_len,
+                    bool extra_data = false) {
+        // Check if option descriptor contains valid option pointer.
+        ASSERT_TRUE(option_desc.option);
+        // Verify option type.
+        EXPECT_EQ(expected_code, option_desc.option->getType());
+        // We may have many different option types being created. Some of them
+        // have dedicated classes derived from Option class. In such case if
+        // we want to verify the option contents against expected_data we have
+        // to prepare raw buffer with the contents of the option. The easiest
+        // way is to call pack() which will prepare on-wire data.
+        util::OutputBuffer buf(option_desc.option->getData().size());
+        option_desc.option->pack(buf);
+        if (extra_data) {
+            // The length of the buffer must be at least equal to size of the
+            // reference data but it can sometimes be greater than that. This is
+            // because some options carry suboptions that increase the overall
+            // length.
+            ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
+                      expected_data_len);
+        } else {
+            ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
+                      expected_data_len);
+        }
+        // Verify that the data is correct. Do not verify suboptions and a header.
+        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+                            expected_data_len));
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by
+    /// removing all subnets and option-data. Reset must
+    /// be performed after each test to make sure that
+    /// contents of the database do not affect result of
+    /// subsequent tests.
+    void resetConfiguration() {
+        ConstElementPtr status;
+
+        string config = "{ \"interface\": [ \"all\" ],"
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet4\": [ ], "
+            "\"option-data\": [ ] }";
+
+        try {
+            ElementPtr json = Element::fromJSON(config);
+            status = configureDhcp4Server(*srv_, json);
+        } catch (const std::exception& ex) {
+            FAIL() << "Fatal error: unable to reset configuration database"
+                   << " after the test. The following configuration was used"
+                   << " to reset database: " << std::endl
+                   << config << std::endl
+                   << " and the following error message was returned:"
+                   << ex.what() << std::endl;
+        }
+
+        // status object must not be NULL
+        if (!status) {
+            FAIL() << "Fatal error: unable to reset configuration database"
+                   << " after the test. Configuration function returned"
+                   << " NULL pointer" << std::endl;
+        }
+
+        comment_ = parseAnswer(rcode_, status);
+        // returned value should be 0 (configuration success)
+        if (rcode_ != 0) {
+            FAIL() << "Fatal error: unable to reset configuration database"
+                   << " after the test. Configuration function returned"
+                   << " error code " << rcode_ << std::endl;
+        }
+    }
+
     Dhcpv4Srv* srv_;
 
     int rcode_;
@@ -213,9 +401,9 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
-    // returned value must be 2 (values error)
+    // returned value must be 1 (values error)
     // as the pool does not belong to that subnet
-    checkResult(status, 2);
+    checkResult(status, 1);
 }
 
 // Goal of this test is to verify if pools can be defined
@@ -248,10 +436,375 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
     EXPECT_EQ(4000, subnet->getValid());
 }
 
+// Goal of this test is to verify that global option
+// data is configured for the subnet if the subnet
+// configuration does not include options configuration.
+TEST_F(Dhcp4ParserTest, optionDataDefaults) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"option_foo\","
+        "    \"code\": 56,"
+        "    \"data\": \"AB CDEF0105\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"option_foo2\","
+        "    \"code\": 23,"
+        "    \"data\": \"01\","
+        "    \"csv-format\": False"
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    ASSERT_EQ(2, options->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(56);
+    // Expect single option with the code equal to 56.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t foo_expected[] = {
+        0xAB, 0xCD, 0xEF, 0x01, 0x05
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+    range = idx.equal_range(23);
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // Do another round of testing with second option.
+    const uint8_t foo2_expected[] = {
+        0x01
+    };
+    testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Goal of this test is to verify options configuration
+// for a single subnet. In particular this test checks
+// that local options configuration overrides global
+// option setting.
+TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ {"
+        "      \"name\": \"option_foo\","
+        "      \"code\": 56,"
+        "      \"data\": \"AB\","
+        "      \"csv-format\": False"
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"option_foo\","
+        "          \"code\": 56,"
+        "          \"data\": \"AB CDEF0105\","
+        "          \"csv-format\": False"
+        "        },"
+        "        {"
+        "          \"name\": \"option_foo2\","
+        "          \"code\": 23,"
+        "          \"data\": \"01\","
+        "          \"csv-format\": False"
+        "        } ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
+    ASSERT_TRUE(subnet);
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    ASSERT_EQ(2, options->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(56);
+    // Expect single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t foo_expected[] = {
+        0xAB, 0xCD, 0xEF, 0x01, 0x05
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+    range = idx.equal_range(23);
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // Do another round of testing with second option.
+    const uint8_t foo2_expected[] = {
+        0x01
+    };
+    testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Goal of this test is to verify options configuration
+// for multiple subnets.
+TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"option_foo\","
+        "          \"code\": 56,"
+        "          \"data\": \"0102030405060708090A\","
+        "          \"csv-format\": False"
+        "        } ]"
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"option_foo2\","
+        "          \"code\": 23,"
+        "          \"data\": \"FF\","
+        "          \"csv-format\": False"
+        "        } ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
+    ASSERT_TRUE(subnet1);
+    Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
+    ASSERT_EQ(1, options1->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range1 =
+        idx1.equal_range(56);
+    // Expect single option with the code equal to 56.
+    ASSERT_EQ(1, std::distance(range1.first, range1.second));
+    const uint8_t foo_expected[] = {
+        0x01, 0x02, 0x03, 0x04, 0x05,
+        0x06, 0x07, 0x08, 0x09, 0x0A
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
+
+    // Test another subnet in the same way.
+    Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
+    ASSERT_TRUE(subnet2);
+    Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
+    ASSERT_EQ(1, options2->size());
+
+    const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range2 =
+        idx2.equal_range(23);
+    ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+    const uint8_t foo2_expected[] = { 0xFF };
+    testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Verify that empty option name is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameEmpty) {
+    // Empty option names not allowed.
+    testInvalidOptionParam("", "name");
+}
+
+// Verify that empty option name with spaces is rejected
+// in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameSpaces) {
+    // Spaces in option names not allowed.
+    testInvalidOptionParam("option foo", "name");
+}
+
+// Verify that negative option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeNegative) {
+    // Check negative option code -4. This should fail too.
+    testInvalidOptionParam("-4", "code");
+}
+
+// Verify that out of bounds option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeNonUint8) {
+    // The valid option codes are uint16_t values so passing
+    // uint16_t maximum value incremented by 1 should result
+    // in failure.
+    testInvalidOptionParam("257", "code");
+}
+
+// Verify that zero option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeZero) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("0", "code");
+}
+
+// Verify that option data which contains non hexadecimal characters
+// is rejected by the configuration.
+TEST_F(Dhcp4ParserTest, optionDataInvalidChar) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("01020R", "data");
+}
+
+// Verify that option data containins '0x' prefix is rejected
+// by the configuration.
+TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("0x0102", "data");
+}
+
+// Verify that option data consisting od an odd number of
+// hexadecimal digits is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionDataOddLength) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("123", "data");
+}
+
+// Verify that either lower or upper case characters are allowed
+// to specify the option data.
+TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
+    ConstElementPtr x;
+    std::string config = createConfigWithOption("0a0b0C0D", "data");
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    ASSERT_EQ(1, options->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(56);
+    // Expect single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t foo_expected[] = {
+        0x0A, 0x0B, 0x0C, 0x0D
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+}
+
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp4ParserTest, stdOptionData) {
+    ConstElementPtr x;
+    std::map<std::string, std::string> params;
+    params["name"] = "nis-servers";
+    // Option code 41 means nis-servers.
+    params["code"] = "41";
+    // Specify option values in a CSV (user friendly) format.
+    params["data"] = "192.0.2.10, 192.0.2.1, 192.0.2.3";
+    params["csv-format"] = "True";
+
+    std::string config = createConfigWithOption(params);
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+    Subnet::OptionContainerPtr options =
+        subnet->getOptionDescriptors("dhcp4");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(1, options->size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(DHO_NIS_SERVERS);
+    // Expect single option with the code equal to NIS_SERVERS option code.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // The actual pointer to the option is held in the option field
+    // in the structure returned.
+    OptionPtr option = range.first->option;
+    ASSERT_TRUE(option);
+    // Option object returned for here is expected to be Option6IA
+    // which is derived from Option. This class is dedicated to
+    // represent standard option IA_NA.
+    boost::shared_ptr<Option4AddrLst> option_addrs =
+        boost::dynamic_pointer_cast<Option4AddrLst>(option);
+    // If cast is unsuccessful than option returned was of a
+    // differnt type than Option6IA. This is wrong.
+    ASSERT_TRUE(option_addrs);
+
+    // Get addresses from the option.
+    Option4AddrLst::AddressContainer addrs = option_addrs->getAddresses();
+    // Verify that the addresses have been configured correctly.
+    ASSERT_EQ(3, addrs.size());
+    EXPECT_EQ("192.0.2.10", addrs[0].toText());
+    EXPECT_EQ("192.0.2.1", addrs[1].toText());
+    EXPECT_EQ("192.0.2.3", addrs[2].toText());
+}
+
 /// This test checks if Uint32Parser can really parse the whole range
 /// and properly err of out of range values. As we can't call Uint32Parser
 /// directly, we are exploiting the fact that it is used to parse global
 /// parameter renew-timer and the results are stored in uint32_defaults.
+/// We get the uint32_defaults using a getUint32Defaults functions which
+/// is defined only to access the values from this test.
 TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
 
     ConstElementPtr status;
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 40a7eef..decd986 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -46,7 +46,6 @@ pkglibexec_PROGRAMS = b10-dhcp6
 
 b10_dhcp6_SOURCES  = main.cc
 b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
-b10_dhcp6_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 b10_dhcp6_SOURCES += config_parser.cc config_parser.h
 b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
@@ -60,14 +59,14 @@ if USE_CLANGPP
 b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
 endif
 
-b10_dhcp6_LDADD  = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp6_LDADD  = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 
 b10_dhcp6dir = $(pkgdatadir)
 b10_dhcp6_DATA = dhcp6.spec
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index da78852..483f38c 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -17,14 +17,16 @@
 #include <config/ccsession.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp6/config_parser.h>
-#include <dhcp6/dbaccess_parser.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dbaccess_parser.h>
+#include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/triplet.h>
 #include <log/logger_support.h>
 #include <util/encode/hex.h>
+#include <util/strutil.h>
 
 #include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
@@ -40,36 +42,41 @@
 
 using namespace std;
 using namespace isc::data;
+using namespace isc::dhcp;
 using namespace isc::asiolink;
 
-namespace isc {
-namespace dhcp {
+namespace {
 
-/// @brief an auxiliary type used for storing an element name and its parser
+/// @brief Auxiliary type used for storing an element name and its parser.
 typedef pair<string, ConstElementPtr> ConfigPair;
 
-/// @brief a factory method that will create a parser for a given element name
-typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
+/// @brief Factory method that will create a parser for a given element name
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
 
-/// @brief a collection of factories that create parsers for specified element names
+/// @brief Collection of factories that create parsers for specified element names
 typedef std::map<std::string, ParserFactory*> FactoryMap;
 
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Collection of elements that store uint32 values (e.g. renew-timer = 900).
 typedef std::map<string, uint32_t> Uint32Storage;
 
-/// @brief a collection of elements that store string values
+/// @brief Collection of elements that store string values.
 typedef std::map<string, string> StringStorage;
 
-/// @brief a collection of pools
+/// @brief Collection of address pools.
 ///
 /// This type is used as intermediate storage, when pools are parsed, but there is
 /// no subnet object created yet to store them.
-typedef std::vector<Pool6Ptr> PoolStorage;
+typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
 
-/// @brief Collection of option descriptors. This container allows searching for
-/// options using the option code or persistency flag. This is useful when merging
-/// existing options with newly configured options.
-typedef Subnet::OptionContainer OptionStorage;
+/// @brief Collection of option descriptors.
+///
+/// This container allows to search options using an option code
+/// or a persistency flag. This is useful when merging existing
+/// options with newly configured options.
+typedef isc::dhcp::Subnet::OptionContainer OptionStorage;
 
 /// @brief Global uint32 parameters that will be used as defaults.
 Uint32Storage uint32_defaults;
@@ -91,7 +98,7 @@ public:
 
     /// @brief Constructor
     ///
-    /// See \ref DhcpConfigParser class for details.
+    /// See @ref DhcpConfigParser class for details.
     ///
     /// @param param_name name of the parsed parameter
     DebugParser(const std::string& param_name)
@@ -100,7 +107,7 @@ public:
 
     /// @brief builds parameter value
     ///
-    /// See \ref DhcpConfigParser class for details.
+    /// See @ref DhcpConfigParser class for details.
     ///
     /// @param new_config pointer to the new configuration
     virtual void build(ConstElementPtr new_config) {
@@ -109,12 +116,12 @@ public:
         value_ = new_config;
     }
 
-    /// @brief pretends to apply the configuration
+    /// @brief Pretends to apply the configuration.
     ///
     /// This is a method required by the base class. It pretends to apply the
     /// configuration, but in fact it only prints the parameter out.
     ///
-    /// See \ref DhcpConfigParser class for details.
+    /// See @ref DhcpConfigParser class for details.
     virtual void commit() {
         // Debug message. The whole DebugParser class is used only for parser
         // debugging, and is not used in production code. It is very convenient
@@ -126,7 +133,7 @@ public:
     /// @brief factory that constructs DebugParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static DhcpConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new DebugParser(param_name));
     }
 
@@ -138,6 +145,86 @@ private:
     ConstElementPtr value_;
 };
 
+
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param param_name name of the parameter.
+    BooleanParser(const std::string& param_name)
+        : storage_(NULL),
+          param_name_(param_name),
+          value_(false) {
+        // Empty parameter name is invalid.
+        if (param_name_.empty()) {
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
+    }
+
+    /// @brief Parse a boolean value.
+    ///
+    /// @param value a value to be parsed.
+    ///
+    /// @throw isc::InvalidOperation if a storage has not been set
+    ///        prior to calling this function
+    /// @throw isc::dhcp::Dhcp6ConfigError if a provided parameter's
+    ///        name is empty.
+    virtual void build(ConstElementPtr value) {
+        if (storage_ == NULL) {
+            isc_throw(isc::InvalidOperation, "parser logic error:"
+                      << " storage for the " << param_name_
+                      << " value has not been set");
+        } else if (param_name_.empty()) {
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
+        // The Config Manager checks if user specified a
+        // valid value for a boolean parameter: True or False.
+        // It is then ok to assume that if str() does not return
+        // 'true' the value is 'false'.
+        value_ = (value->str() == "true") ? true : false;
+    }
+
+    /// @brief Put a parsed value to the storage.
+    virtual void commit() {
+        if (storage_ != NULL && !param_name_.empty()) {
+            (*storage_)[param_name_] = value_;
+        }
+    }
+
+    /// @brief Create an instance of the boolean parser.
+    ///
+    /// @param param_name name of the parameter for which the
+    ///        parser is created.
+    static DhcpConfigParser* factory(const std::string& param_name) {
+        return (new BooleanParser(param_name));
+    }
+
+    /// @brief Set the storage for parsed value.
+    ///
+    /// This function must be called prior to calling build.
+    ///
+    /// @param storage a pointer to the storage where parsed data
+    ///        is to be stored.
+    void setStorage(BooleanStorage* storage) {
+        storage_ = storage;
+    }
+
+private:
+    /// Pointer to the storage where parsed value is stored.
+    BooleanStorage* storage_;
+    /// Name of the parameter which value is parsed with this parser.
+    std::string param_name_;
+    /// Parsed value.
+    bool value_;
+};
+
 /// @brief Configuration parser for uint32 parameters
 ///
 /// This class is a generic parser that is able to handle any uint32 integer
@@ -145,10 +232,10 @@ private:
 /// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
 /// in subnet config), it can be pointed to a different storage, using
 /// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
 ///
 /// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
 ///
 /// @todo this class should be turned into the template class which
 /// will handle all uintX_types of data (see ticket #2415).
@@ -156,18 +243,29 @@ class Uint32Parser : public DhcpConfigParser {
 public:
 
     /// @brief constructor for Uint32Parser
+    ///
     /// @param param_name name of the configuration parameter being parsed
     Uint32Parser(const std::string& param_name)
-        :storage_(&uint32_defaults), param_name_(param_name) {
+        : storage_(&uint32_defaults),
+          param_name_(param_name) {
+        // Empty parameter name is invalid.
+        if (param_name_.empty()) {
+            isc_throw(Dhcp6ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
     }
 
-    /// @brief builds parameter value
-    ///
-    /// Parses configuration entry and stores it in a storage. See
-    /// \ref Uint32Parser::setStorage() for details.
+    /// @brief Parses configuration configuration parameter as uint32_t.
     ///
     /// @param value pointer to the content of parsed values
+    /// @throw isc::dhcp::Dhcp6ConfigError if failed to parse
+    ///        the configuration parameter as uint32_t value.
     virtual void build(ConstElementPtr value) {
+        if (param_name_.empty()) {
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
+
         bool parse_error = false;
         // Cast the provided value to int64 value to check.
         int64_t int64value = 0;
@@ -181,51 +279,45 @@ public:
             parse_error = true;
         }
         if (!parse_error) {
+            // Check that the value is not out of bounds.
             if ((int64value < 0) ||
                 (int64value > std::numeric_limits<uint32_t>::max())) {
                 parse_error = true;
             } else {
-                try {
-                    value_ = boost::lexical_cast<uint32_t>(value->str());
-                } catch (const boost::bad_lexical_cast &) {
-                    parse_error = true;
-                }
+                // A value is not out of bounds so let's cast it to
+                // the uint32_t type.
+                value_ = static_cast<uint32_t>(int64value);
             }
 
         }
-
+        // Invalid value provided.
         if (parse_error) {
-            isc_throw(BadValue, "Failed to parse value " << value->str()
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "Failed to parse value " << value->str()
                       << " as unsigned 32-bit integer.");
         }
-
-        // If a given parameter already exists in the storage we override
-        // its value. If it doesn't we insert a new element.
-        (*storage_)[param_name_] = value_;
     }
 
-    /// @brief does nothing
-    ///
-    /// This method is required for all parsers. The value itself
-    /// is not commited anywhere. Higher level parsers are expected to
-    /// use values stored in the storage, e.g. renew-timer for a given
-    /// subnet is stored in subnet-specific storage. It is not commited
-    /// here, but is rather used by \ref Subnet6Parser when constructing
-    /// the subnet.
-    virtual void commit() { }
+    /// @brief Stores the parsed uint32_t value in a storage.
+    virtual void commit() {
+        if (storage_ != NULL) {
+            // If a given parameter already exists in the storage we override
+            // its value. If it doesn't we insert a new element.
+            (*storage_)[param_name_] = value_;
+        }
+    }
 
-    /// @brief factory that constructs Uint32Parser objects
+    /// @brief Factory that constructs Uint32Parser objects.
     ///
-    /// @param param_name name of the parameter to be parsed
-    static DhcpConfigParser* Factory(const std::string& param_name) {
+    /// @param param_name name of the parameter to be parsed.
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new Uint32Parser(param_name));
     }
 
-    /// @brief sets storage for value of this parameter
+    /// @brief Sets storage for value of this parameter.
     ///
-    /// See \ref dhcpv6ConfigInherit for details.
+    /// See @ref dhcpv6ConfigInherit for details.
     ///
-    /// @param storage pointer to the storage container
+    /// @param storage pointer to the storage container.
     void setStorage(Uint32Storage* storage) {
         storage_ = storage;
     }
@@ -233,10 +325,8 @@ public:
 private:
     /// pointer to the storage, where parsed value will be stored
     Uint32Storage* storage_;
-
     /// name of the parameter to be parsed
     std::string param_name_;
-
     /// the actual parsed value
     uint32_t value_;
 };
@@ -248,54 +338,60 @@ private:
 /// (string_defaults). If used in smaller scopes (e.g. to parse parameters
 /// in subnet config), it can be pointed to a different storage, using the
 /// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
 ///
 /// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
 class StringParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor for StringParser
+    ///
     /// @param param_name name of the configuration parameter being parsed
     StringParser(const std::string& param_name)
-        :storage_(&string_defaults), param_name_(param_name) {
+        : storage_(&string_defaults),
+          param_name_(param_name) {
+        // Empty parameter name is invalid.
+        if (param_name_.empty()) {
+            isc_throw(Dhcp6ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
     }
 
     /// @brief parses parameter value
     ///
-    /// Parses configuration entry and stores it in storage. See
-    /// \ref setStorage() for details.
+    /// Parses configuration parameter's value as string.
     ///
     /// @param value pointer to the content of parsed values
+    /// @throws Dhcp6ConfigError if the parsed parameter's name is empty.
     virtual void build(ConstElementPtr value) {
+        if (param_name_.empty()) {
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+                      << "empty parameter name provided");
+        }
         value_ = value->str();
         boost::erase_all(value_, "\"");
-
-        // If a given parameter already exists in the storage we override
-        // its value. If it doesn't we insert a new element.
-        (*storage_)[param_name_] = value_;
     }
 
-    /// @brief does nothing
-    ///
-    /// This method is required for all parsers. The value itself
-    /// is not commited anywhere. Higher level parsers are expected to
-    /// use values stored in the storage, e.g. renew-timer for a given
-    /// subnet is stored in subnet-specific storage. It is not commited
-    /// here, but is rather used by its parent parser when constructing
-    /// an object, e.g. the subnet.
-    virtual void commit() { }
+    /// @brief Stores the parsed value in a storage.
+    virtual void commit() {
+        if (storage_ != NULL && !param_name_.empty()) {
+            // If a given parameter already exists in the storage we override
+            // its value. If it doesn't we insert a new element.
+            (*storage_)[param_name_] = value_;
+        }
+    }
 
-    /// @brief factory that constructs StringParser objects
+    /// @brief Factory that constructs StringParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static DhcpConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new StringParser(param_name));
     }
 
-    /// @brief sets storage for value of this parameter
+    /// @brief Sets storage for value of this parameter.
     ///
-    /// See \ref dhcpv6ConfigInherit for details.
+    /// See @ref dhcpv6ConfigInherit for details.
     ///
     /// @param storage pointer to the storage container
     void setStorage(StringStorage* storage) {
@@ -303,17 +399,14 @@ public:
     }
 
 private:
-    /// pointer to the storage, where parsed value will be stored
+    /// Pointer to the storage, where parsed value will be stored
     StringStorage* storage_;
-
-    /// name of the parameter to be parsed
+    /// Name of the parameter to be parsed
     std::string param_name_;
-
-    /// the actual parsed value
+    /// The actual parsed value
     std::string value_;
 };
 
-
 /// @brief parser for interface list definition
 ///
 /// This parser handles Dhcp6/interface entry.
@@ -334,7 +427,7 @@ public:
     /// @throw BadValue if supplied parameter name is not "interface"
     InterfaceListConfigParser(const std::string& param_name) {
         if (param_name != "interface") {
-            isc_throw(BadValue, "Internal error. Interface configuration "
+            isc_throw(isc::BadValue, "Internal error. Interface configuration "
                       "parser called for the wrong parameter: " << param_name);
         }
     }
@@ -360,7 +453,7 @@ public:
     /// @brief factory that constructs InterfaceListConfigParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static DhcpConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new InterfaceListConfigParser(param_name));
     }
 
@@ -384,7 +477,7 @@ public:
 
     /// @brief constructor.
     PoolParser(const std::string& /*param_name*/)
-        :pools_(NULL) {
+        : pools_(NULL) {
         // ignore parameter name, it is always Dhcp6/subnet6[X]/pool
     }
 
@@ -394,11 +487,13 @@ public:
     /// No validation is done at this stage, everything is interpreted as
     /// interface name.
     /// @param pools_list list of pools defined for a subnet
-    /// @throw BadValue if storage was not specified (setStorage() not called)
+    /// @throw isc::InvalidOperation if storage was not specified
+    ///        (setStorage() not called)
     void build(ConstElementPtr pools_list) {
+
         // setStorage() should have been called before build
         if (!pools_) {
-            isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+            isc_throw(isc::InvalidOperation, "parser logic error: no pool storage set,"
                       " but pool parser asked to parse pools");
         }
 
@@ -434,12 +529,12 @@ public:
                     // be checked in Pool6 constructor.
                     len = boost::lexical_cast<int>(prefix_len);
                 } catch (...)  {
-                    isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+                    isc_throw(Dhcp6ConfigError, "failed to parse pool "
                               "definition: " << text_pool->stringValue());
                 }
 
                 Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
-                pools_->push_back(pool);
+                local_pools_.push_back(pool);
                 continue;
             }
 
@@ -452,11 +547,11 @@ public:
 
                 Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
 
-                pools_->push_back(pool);
+                local_pools_.push_back(pool);
                 continue;
             }
 
-            isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+            isc_throw(Dhcp6ConfigError, "failed to parse pool definition:"
                       << text_pool->stringValue() <<
                       ". Does not contain - (for min-max) nor / (prefix/len)");
         }
@@ -464,24 +559,29 @@ public:
 
     /// @brief sets storage for value of this parameter
     ///
-    /// See \ref dhcpv6ConfigInherit for details.
+    /// See @ref dhcpv6ConfigInherit for details.
     ///
     /// @param storage pointer to the storage container
     void setStorage(PoolStorage* storage) {
         pools_ = storage;
     }
 
-    /// @brief does nothing.
-    ///
-    /// This method is required for all parsers. The value itself
-    /// is not commited anywhere. Higher level parsers (for subnet) are expected
-    /// to use values stored in the storage.
-    virtual void commit() {}
+    /// @brief Stores the parsed values in a storage provided
+    ///        by an upper level parser.
+    virtual void commit() {
+        if (pools_) {
+            // local_pools_ holds the values produced by the build function.
+            // At this point parsing should have completed successfuly so
+            // we can append new data to the supplied storage.
+            pools_->insert(pools_->end(), local_pools_.begin(),
+                           local_pools_.end());
+        }
+    }
 
     /// @brief factory that constructs PoolParser objects
     ///
     /// @param param_name name of the parameter to be parsed
-    static DhcpConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new PoolParser(param_name));
     }
 
@@ -491,18 +591,22 @@ private:
     /// This is typically a storage somewhere in Subnet parser
     /// (an upper level parser).
     PoolStorage* pools_;
+    /// A temporary storage for pools configuration. It is a
+    /// storage where pools are stored by build function.
+    PoolStorage local_pools_;
 };
 
+
 /// @brief Parser for option data value.
 ///
 /// This parser parses configuration entries that specify value of
 /// a single option. These entries include option name, option code
-/// and data carried by the option. If parsing is successful than an
+/// and data carried by the option. If parsing is successful then an
 /// instance of an option is created and added to the storage provided
 /// by the calling class.
 ///
 /// @todo This class parses and validates the option name. However it is
-/// not used anywhere util support for option spaces is implemented
+/// not used anywhere until support for option spaces is implemented
 /// (see tickets #2319, #2314). When option spaces are implemented
 /// there will be a way to reference the particular option using
 /// its type (code) or option name.
@@ -535,6 +639,7 @@ public:
     /// calling build.
     /// @throw isc::BadValue if option data storage is invalid.
     virtual void build(ConstElementPtr option_data_entries) {
+
         if (options_ == NULL) {
             isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
                       "parsing option data.");
@@ -543,31 +648,45 @@ public:
             ParserPtr parser;
             if (param.first == "name") {
                 boost::shared_ptr<StringParser>
-                    name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+                    name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
                 if (name_parser) {
                     name_parser->setStorage(&string_values_);
                     parser = name_parser;
                 }
             } else if (param.first == "code") {
                 boost::shared_ptr<Uint32Parser>
-                    code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+                    code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
                 if (code_parser) {
                     code_parser->setStorage(&uint32_values_);
                     parser = code_parser;
                 }
             } else if (param.first == "data") {
                 boost::shared_ptr<StringParser>
-                    value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+                    value_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
                 if (value_parser) {
                     value_parser->setStorage(&string_values_);
                     parser = value_parser;
                 }
+            } else if (param.first == "csv-format") {
+                boost::shared_ptr<BooleanParser>
+                    value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
+                if (value_parser) {
+                    value_parser->setStorage(&boolean_values_);
+                    parser = value_parser;
+                }
             } else {
                 isc_throw(Dhcp6ConfigError,
-                          "Parser error: option-data parameter not supported: "
+                          "parser error: option-data parameter not supported: "
                           << param.first);
             }
             parser->build(param.second);
+            // Before we can create an option we need to get the data from
+            // the child parsers. The only way to do it is to invoke commit
+            // on them so as they store the values in appropriate storages
+            // that this class provided to them. Note that this will not
+            // modify values stored in the global storages so the configuration
+            // will remain consistent even parsing fails somewhere further on.
+            parser->commit();
         }
         // Try to create the option instance.
         createOption();
@@ -583,20 +702,20 @@ public:
     /// remain un-modified.
     virtual void commit() {
         if (options_ == NULL) {
-            isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+            isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
                       "commiting option data.");
         } else  if (!option_descriptor_.option) {
             // Before we can commit the new option should be configured. If it is not
             // than somebody must have called commit() before build().
-            isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+            isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
                       " thus there is nothing to commit. Has build() been called?");
         }
         uint16_t opt_type = option_descriptor_.option->getType();
-        Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+        isc::dhcp::Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
         // Try to find options with the particular option code in the main
         // storage. If found, remove these options because they will be
         // replaced with new one.
-        Subnet::OptionContainerTypeRange range =
+        isc::dhcp::Subnet::OptionContainerTypeRange range =
             idx.equal_range(opt_type);
         if (std::distance(range.first, range.second) > 0) {
             idx.erase(range.first, range.second);
@@ -633,6 +752,7 @@ private:
     /// @throw Dhcp6ConfigError if parameters provided in the configuration
     /// are invalid.
     void createOption() {
+
         // Option code is held in the uint32_t storage but is supposed to
         // be uint16_t value. We need to check that value in the configuration
         // does not exceed range of uint16_t and is not zero.
@@ -658,16 +778,25 @@ private:
         }
 
         // Get option data from the configuration database ('data' field).
-        // Option data is specified by the user as case insensitive string
-        // of hexadecimal digits for each option.
-        std::string option_data = getStringParam("data");
-        // Transform string of hexadecimal digits into binary format.
+        const std::string option_data = getStringParam("data");
+        const bool csv_format = getBooleanParam("csv-format");
         std::vector<uint8_t> binary;
-        try {
-            util::encode::decodeHex(option_data, binary);
-        } catch (...) {
-            isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
-                      << " string of hexadecimal digits: " << option_data);
+        std::vector<std::string> data_tokens;
+        if (csv_format) {
+            // If the option data is specified as a string of comma
+            // separated values then we need to split this string into
+            // individual values - each value will be used to initialize
+            // one data field of an option.
+            data_tokens = isc::util::str::tokens(option_data, ",");
+        } else {
+            // Otherwise, the option data is specified as a string of
+            // hexadecimal digits that we have to turn into binary format.
+            try {
+                isc::util::encode::decodeHex(option_data, binary);
+            } catch (...) {
+                isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
+                          << " string of hexadecimal digits: " << option_data);
+            }
         }
         // Get all existing DHCPv6 option definitions. The one that matches
         // our option will be picked and used to create it.
@@ -687,6 +816,12 @@ private:
                       << " for the same option code. This will be supported once"
                       << " there option spaces are implemented.");
         } else if (num_defs == 0) {
+            if (csv_format) {
+                isc_throw(Dhcp6ConfigError, "the CSV option data format can be"
+                          " used to specify values for an option that has a"
+                          " definition. The option with code " << option_code
+                          << " does not have a definition.");
+            }
             // @todo We have a limited set of option definitions intiialized at the moment.
             // In the future we want to initialize option definitions for all options.
             // Consequently an error will be issued if an option definition does not exist
@@ -704,7 +839,9 @@ private:
             // use it to create the option instance.
             const OptionDefinitionPtr& def = *(range.first);
             try {
-                OptionPtr option = def->optionFactory(Option::V6, option_code, binary);
+                OptionPtr option = csv_format ?
+                    def->optionFactory(Option::V6, option_code, data_tokens) :
+                    def->optionFactory(Option::V6, option_code, binary);
                 Subnet::OptionDescriptor desc(option, false);
                 option_descriptor_.option = option;
                 option_descriptor_.persistent = false;
@@ -719,11 +856,13 @@ private:
     /// @brief Get a parameter from the strings storage.
     ///
     /// @param param_id parameter identifier.
-    /// @throw Dhcp6ConfigError if parameter has not been found.
+    ///
+    /// @throw Dhcp6ConfigError if a parameter has not been found.
+    /// @return a value of the string parameter.
     std::string getStringParam(const std::string& param_id) const {
         StringStorage::const_iterator param = string_values_.find(param_id);
         if (param == string_values_.end()) {
-            isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
                       << " '" << param_id << "' not specified");
         }
         return (param->second);
@@ -732,11 +871,28 @@ private:
     /// @brief Get a parameter from the uint32 values storage.
     ///
     /// @param param_id parameter identifier.
-    /// @throw Dhcp6ConfigError if parameter has not been found.
+    ///
+    /// @throw Dhcp6ConfigError if a parameter has not been found.
+    /// @return a value of the uint32_t parameter.
     uint32_t getUint32Param(const std::string& param_id) const {
         Uint32Storage::const_iterator param = uint32_values_.find(param_id);
         if (param == uint32_values_.end()) {
-            isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
+                      << " '" << param_id << "' not specified");
+        }
+        return (param->second);
+    }
+
+    /// @brief Get a parameter from the boolean values storage.
+    ///
+    /// @param param_id parameter identifier.
+    ///
+    /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
+    /// @return a value of the boolean parameter.
+    bool getBooleanParam(const std::string& param_id) const {
+        BooleanStorage::const_iterator param = boolean_values_.find(param_id);
+        if (param == boolean_values_.end()) {
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
                       << " '" << param_id << "' not specified");
         }
         return (param->second);
@@ -746,11 +902,13 @@ private:
     Uint32Storage uint32_values_;
     /// Storage for string values (e.g. option name or data).
     StringStorage string_values_;
+    /// Storage for boolean values.
+    BooleanStorage boolean_values_;
     /// Pointer to options storage. This storage is provided by
     /// the calling class and is shared by all OptionDataParser objects.
     OptionStorage* options_;
     /// Option descriptor holds newly configured option.
-    Subnet::OptionDescriptor option_descriptor_;
+    isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
 };
 
 /// @brief Parser for option data values within a subnet.
@@ -816,7 +974,7 @@ public:
     /// @param param_name param name.
     ///
     /// @return DhcpConfigParser object.
-    static DhcpConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new OptionDataListParser(param_name));
     }
 
@@ -847,6 +1005,8 @@ public:
     /// @brief parses parameter value
     ///
     /// @param subnet pointer to the content of subnet definition
+    ///
+    /// @throw isc::Dhcp6ConfigError if subnet configuration parsing failed.
     void build(ConstElementPtr subnet) {
 
         BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
@@ -858,60 +1018,119 @@ public:
             // a setStorage and build methods are invoked.
 
             // Try uint32 type parser.
-            if (buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
-                                                          param.second)) {
-                // Storage set, build invoked on the parser, proceed with
-                // next configuration element.
-                continue;
-            }
-            // Try string type parser.
-            if (buildParser<StringParser, StringStorage >(parser, string_values_,
-                                                          param.second)) {
-                continue;
-            }
-            // Try pools parser.
-            if (buildParser<PoolParser, PoolStorage >(parser, pools_,
-                                                      param.second)) {
-                continue;
-            }
-            // Try option data parser.
-            if (buildParser<OptionDataListParser, OptionStorage >(parser, options_,
-                                                                  param.second)) {
-                continue;
+            if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
+                                                           param.second) &&
+                // Try string type parser.
+                !buildParser<StringParser, StringStorage >(parser, string_values_,
+                                                           param.second) &&
+                // Try pool parser.
+                !buildParser<PoolParser, PoolStorage >(parser, pools_,
+                                                       param.second) &&
+                // Try option data parser.
+                !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
+                                                                   param.second)) {
+                // Appropriate parsers are created in the createSubnet6ConfigParser
+                // and they should be limited to those that we check here for. Thus,
+                // if we fail to find a matching parser here it is a programming error.
+                isc_throw(Dhcp6ConfigError, "failed to find suitable parser");
             }
         }
-        // Ok, we now have subnet parsed
-    }
 
-    /// @brief commits received configuration.
-    ///
-    /// This method does most of the configuration. Many other parsers are just
-    /// storing the values that are actually consumed here. Pool definitions
-    /// created in other parsers are used here and added to newly created Subnet6
-    /// objects. Subnet6 are then added to DHCP CfgMgr.
-    void commit() {
-        // Invoke commit on all sub-data parsers.
+        // In order to create new subnet we need to get the data out
+        // of the child parsers first. The only way to do it is to
+        // invoke commit on them because it will make them write
+        // parsed data into storages we have supplied.
+        // Note that triggering commits on child parsers does not
+        // affect global data because we supplied pointers to storages
+        // local to this object. Thus, even if this method fails
+        // later on, the configuration remains consistent.
         BOOST_FOREACH(ParserPtr parser, parsers_) {
             parser->commit();
         }
 
+        // Create a subnet.
+        createSubnet();
+    }
+
+    /// @brief Adds the created subnet to a server's configuration.
+    void commit() {
+        if (subnet_) {
+            isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
+        }
+    }
+
+private:
+
+    /// @brief Set storage for a parser and invoke build.
+    ///
+    /// This helper method casts the provided parser pointer to the specified
+    /// type. If the cast is successful it sets the corresponding storage for
+    /// this parser, invokes build on it and saves the parser.
+    ///
+    /// @tparam T parser type to which parser argument should be cast.
+    /// @tparam Y storage type for the specified parser type.
+    /// @param parser parser on which build must be invoked.
+    /// @param storage reference to a storage that will be set for a parser.
+    /// @param subnet subnet element read from the configuration and being parsed.
+    /// @return true if parser pointer was successfully cast to specialized
+    /// parser type provided as Y.
+    template<typename T, typename Y>
+    bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+        // We need to cast to T in order to set storage for the parser.
+        boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+        // It is common that this cast is not successful because we try to cast to all
+        // supported parser types as we don't know the type of a parser in advance.
+        if (cast_parser) {
+            // Cast, successful so we go ahead with setting storage and actual parse.
+            cast_parser->setStorage(&storage);
+            parser->build(subnet);
+            parsers_.push_back(parser);
+            // We indicate that cast was successful so as the calling function
+            // may skip attempts to cast to other parser types and proceed to
+            // next element.
+            return (true);
+        }
+        // It was not successful. Indicate that another parser type
+        // should be tried.
+        return (false);
+    }
+
+    /// @brief Create a new subnet using a data from child parsers.
+    ///
+    /// @throw isc::dhcp::Dhcp6ConfigError if subnet configuration parsing failed.
+    void createSubnet() {
+        
+        // Find a subnet string.
         StringStorage::const_iterator it = string_values_.find("subnet");
         if (it == string_values_.end()) {
             isc_throw(Dhcp6ConfigError,
                       "Mandatory subnet definition in subnet missing");
         }
+        // Remove any spaces or tabs.
         string subnet_txt = it->second;
         boost::erase_all(subnet_txt, " ");
         boost::erase_all(subnet_txt, "\t");
-
+        // The subnet format is prefix/len. We are going to extract
+        // the prefix portion of a subnet string to create IOAddress
+        // object from it. IOAddress will be passed to the Subnet's
+        // constructor later on. In order to extract the prefix we
+        // need to get all characters preceding "/".
         size_t pos = subnet_txt.find("/");
         if (pos == string::npos) {
             isc_throw(Dhcp6ConfigError,
                       "Invalid subnet syntax (prefix/len expected):" << it->second);
         }
+
+        // Try to create the address object. It also validates that
+        // the address syntax is ok.
         IOAddress addr(subnet_txt.substr(0, pos));
         uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
 
+        // Get all 'time' parameters using inheritance.
+        // If the subnet-specific value is defined then use it, else
+        // use the global value. The global value must always be
+        // present. If it is not, it is an internal error and exception
+        // is thrown.
         Triplet<uint32_t> t1 = getParam("renew-timer");
         Triplet<uint32_t> t2 = getParam("rebind-timer");
         Triplet<uint32_t> pref = getParam("preferred-lifetime");
@@ -925,14 +1144,16 @@ public:
 
         LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
 
-        Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+        // Create a new subnet.
+        subnet_.reset(new Subnet6(addr, len, t1, t2, pref, valid));
 
+        // Add pools to it.
         for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
-            subnet->addPool6(*it);
+            subnet_->addPool6(*it);
         }
 
-        const Subnet::OptionContainer& options = subnet->getOptions();
-        const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+        Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
+        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
         // Add subnet specific options.
         BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
@@ -941,7 +1162,7 @@ public:
                 LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
                     .arg(desc.option->getType()).arg(addr.toText());
             }
-            subnet->addOption(desc.option);
+            subnet_->addOption(desc.option, false, "dhcp6");
         }
 
         // Check all global options and add them to the subnet object if
@@ -951,6 +1172,8 @@ public:
         BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
             // Get all options specified locally in the subnet and having
             // code equal to global option's code.
+            Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
+            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
             Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
             // @todo: In the future we will be searching for options using either
             // an option code or namespace. Currently we have only the option
@@ -961,91 +1184,42 @@ public:
             // want to issue a warning about dropping the configuration of
             // a global option if one already exsists.
             if (std::distance(range.first, range.second) == 0) {
-                subnet->addOption(desc.option);
+                subnet_->addOption(desc.option, false, "dhcp6");
             }
         }
-
-        CfgMgr::instance().addSubnet6(subnet);
-    }
-
-private:
-
-    /// @brief Set storage for a parser and invoke build.
-    ///
-    /// This helper method casts the provided parser pointer to the specified
-    /// type. If the cast is successful it sets the corresponding storage for
-    /// this parser, invokes build on it and saves the parser.
-    ///
-    /// @tparam T parser type to which parser argument should be cast.
-    /// @tparam Y storage type for the specified parser type.
-    /// @param parser parser on which build must be invoked.
-    /// @param storage reference to a storage that will be set for a parser.
-    /// @param subnet subnet element read from the configuration and being parsed.
-    /// @return true if parser pointer was successfully cast to specialized
-    /// parser type provided as Y.
-    template<typename T, typename Y>
-    bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
-        // We need to cast to T in order to set storage for the parser.
-        boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
-        // It is common that this cast is not successful because we try to cast to all
-        // supported parser types as we don't know the type of a parser in advance.
-        if (cast_parser) {
-            // Cast, successful so we go ahead with setting storage and actual parse.
-            cast_parser->setStorage(&storage);
-            parser->build(subnet);
-            parsers_.push_back(parser);
-            // We indicate that cast was successful so as the calling function
-            // may skip attempts to cast to other parser types and proceed to
-            // next element.
-            return (true);
-        }
-        // It was not successful. Indicate that another parser type
-        // should be tried.
-        return (false);
     }
 
     /// @brief creates parsers for entries in subnet definition
     ///
-    /// @todo Add subnet-specific things here (e.g. subnet-specific options)
-    ///
     /// @param config_id name od the entry
+    ///
     /// @return parser object for specified entry name
-    /// @throw NotImplemented if trying to create a parser for unknown config element
+    /// @throw isc::dhcp::Dhcp6ConfigError if trying to create a parser
+    ///        for unknown config element
     DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
         FactoryMap factories;
 
-        factories.insert(pair<string, ParserFactory*>(
-                             "preferred-lifetime", Uint32Parser::Factory));
-        factories.insert(pair<string, ParserFactory*>(
-                             "valid-lifetime", Uint32Parser::Factory));
-        factories.insert(pair<string, ParserFactory*>(
-                             "renew-timer", Uint32Parser::Factory));
-        factories.insert(pair<string, ParserFactory*>(
-                             "rebind-timer", Uint32Parser::Factory));
-
-        factories.insert(pair<string, ParserFactory*>(
-                             "subnet", StringParser::Factory));
-
-        factories.insert(pair<string, ParserFactory*>(
-                             "pool", PoolParser::Factory));
-
-        factories.insert(pair<string, ParserFactory*>(
-                             "option-data", OptionDataListParser::Factory));
-
+        factories["preferred-lifetime"] = Uint32Parser::factory;
+        factories["valid-lifetime"] = Uint32Parser::factory;
+        factories["renew-timer"] = Uint32Parser::factory;
+        factories["rebind-timer"] = Uint32Parser::factory;
+        factories["subnet"] = StringParser::factory;
+        factories["pool"] = PoolParser::factory;
+        factories["option-data"] = OptionDataListParser::factory;
 
         FactoryMap::iterator f = factories.find(config_id);
         if (f == factories.end()) {
             // Used for debugging only.
             // return new DebugParser(config_id);
 
-            isc_throw(NotImplemented,
-                      "Parser error: Subnet6 parameter not supported: "
+            isc_throw(isc::dhcp::Dhcp6ConfigError,
+                      "parser error: subnet6 parameter not supported: "
                       << config_id);
         }
         return (f->second(config_id));
     }
 
-    /// @brief returns value for a given parameter (after using inheritance)
+    /// @brief Returns value for a given parameter (after using inheritance)
     ///
     /// This method implements inheritance.  For a given parameter name, it first
     /// checks if there is a global value for it and overwrites it with specific
@@ -1054,7 +1228,7 @@ private:
     /// @param name name of the parameter
     /// @return triplet with the parameter name
     /// @throw Dhcp6ConfigError when requested parameter is not present
-    Triplet<uint32_t> getParam(const std::string& name) {
+    isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
         uint32_t value = 0;
         bool found = false;
         Uint32Storage::iterator global = uint32_defaults.find(name);
@@ -1070,9 +1244,9 @@ private:
         }
 
         if (found) {
-            return (Triplet<uint32_t>(value));
+            return (isc::dhcp::Triplet<uint32_t>(value));
         } else {
-            isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+            isc_throw(isc::dhcp::Dhcp6ConfigError, "Mandatory parameter " << name
                       << " missing (no global default and no subnet-"
                       << "specific value)");
         }
@@ -1092,6 +1266,9 @@ private:
 
     /// parsers are stored here
     ParserCollection parsers_;
+
+    /// Pointer to the created subnet object.
+    isc::dhcp::Subnet6Ptr subnet_;
 };
 
 /// @brief this class parses a list of subnets
@@ -1137,7 +1314,7 @@ public:
         // the old one and replace with the new one.
 
         // remove old subnets
-        CfgMgr::instance().deleteSubnets6();
+        isc::dhcp::CfgMgr::instance().deleteSubnets6();
 
         BOOST_FOREACH(ParserPtr subnet, subnets_) {
             subnet->commit();
@@ -1148,7 +1325,7 @@ public:
     /// @brief Returns Subnet6ListConfigParser object
     /// @param param_name name of the parameter
     /// @return Subnets6ListConfigParser object
-    static DhcpConfigParser* Factory(const std::string& param_name) {
+    static DhcpConfigParser* factory(const std::string& param_name) {
         return (new Subnets6ListConfigParser(param_name));
     }
 
@@ -1156,6 +1333,11 @@ public:
     ParserCollection subnets_;
 };
 
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
 /// @brief creates global parsers
 ///
 /// This method creates global parsers that parse global parameters, i.e.
@@ -1167,27 +1349,15 @@ public:
 DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
     FactoryMap factories;
 
-    factories.insert(pair<string, ParserFactory*>(
-                         "preferred-lifetime", Uint32Parser::Factory));
-    factories.insert(pair<string, ParserFactory*>(
-                         "valid-lifetime", Uint32Parser::Factory));
-    factories.insert(pair<string, ParserFactory*>(
-                         "renew-timer", Uint32Parser::Factory));
-    factories.insert(pair<string, ParserFactory*>(
-                         "rebind-timer", Uint32Parser::Factory));
-
-    factories.insert(pair<string, ParserFactory*>(
-                         "interface", InterfaceListConfigParser::Factory));
-    factories.insert(pair<string, ParserFactory*>(
-                         "subnet6", Subnets6ListConfigParser::Factory));
-
-    factories.insert(pair<string, ParserFactory*>(
-                         "option-data", OptionDataListParser::Factory));
-
-    factories.insert(pair<string, ParserFactory*>(
-                         "version", StringParser::Factory));
-    factories.insert(pair<string, ParserFactory*>(
-                         "lease-database", DbAccessParser::factory));
+    factories["preferred-lifetime"] = Uint32Parser::factory;
+    factories["valid-lifetime"] = Uint32Parser::factory;
+    factories["renew-timer"] = Uint32Parser::factory;
+    factories["rebind-timer"] = Uint32Parser::factory;
+    factories["interface"] = InterfaceListConfigParser::factory;
+    factories["subnet6"] = Subnets6ListConfigParser::factory;
+    factories["option-data"] = OptionDataListParser::factory;
+    factories["version"] = StringParser::factory;
+    factories["lease-database"] = DbAccessParser::factory;
 
     FactoryMap::iterator f = factories.find(config_id);
     if (f == factories.end()) {
@@ -1201,21 +1371,6 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
     return (f->second(config_id));
 }
 
-/// @brief configures DHCPv6 server
-///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
-///
-/// This method does not throw. It catches all exceptions and returns them as
-/// reconfiguration statuses. It may return the following response codes:
-/// 0 - configuration successful
-/// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
-///
-/// @param config_set a new configuration for DHCPv6 server
-/// @return answer that contains result of reconfiguration
-/// @throw Dhcp6ConfigError if trying to create a parser for NULL config
 ConstElementPtr
 configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
     if (!config_set) {
@@ -1229,42 +1384,121 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
 
-    ParserCollection parsers;
+    // Some of the values specified in the configuration depend on
+    // other values. Typically, the values in the subnet6 structure
+    // depend on the global values. Thus we need to make sure that
+    // the global values are processed first and that they can be
+    // accessed by the subnet6 parsers. We separate parsers that
+    // should process data first (independent_parsers) from those
+    // that must process data when the independent data is already
+    // processed (dependent_parsers).
+    ParserCollection independent_parsers;
+    ParserCollection dependent_parsers;
+
+    // The subnet parsers implement data inheritance by directly
+    // accessing global storages. For this reason the global data
+    // parsers must store the parsed data into global storages
+    // immediately. This may cause data inconsistency if the
+    // parsing operation fails after the global storage has been
+    // modified. We need to preserve the original global data here
+    // so as we can rollback changes when an error occurs.
+    Uint32Storage uint32_local(uint32_defaults);
+    StringStorage string_local(string_defaults);
+    OptionStorage option_local(option_defaults);
+
+    // answer will hold the result.
+    ConstElementPtr answer;
+    // rollback informs whether error occured and original data
+    // have to be restored to global storages.
+    bool rollback = false;
+    string current_parser;  // For error messages
     try {
+        // Iterate over all independent parsers first (all but subnet6)
+        // and try to parse the data.
         BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+            if (config_pair.first != "subnet6") {
+                current_parser = config_pair.first;
+                ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED1)
+                          .arg(current_parser);
+                independent_parsers.push_back(parser);
+                parser->build(config_pair.second);
+                // The commit operation here may modify the global storage
+                // but we need it so as the subnet6 parser can access the
+                // parsed data.
+                parser->commit();
+            }
+        }
 
-            ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
-            parser->build(config_pair.second);
-            parsers.push_back(parser);
+        // Process dependent configuration data.
+        BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+            if (config_pair.first == "subnet6") {
+                current_parser = config_pair.first;
+                ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED2)
+                          .arg(current_parser);
+                dependent_parsers.push_back(parser);
+                parser->build(config_pair.second);
+            }
         }
+
     } catch (const isc::Exception& ex) {
-        ConstElementPtr answer = isc::config::createAnswer(1,
-                                 string("Configuration parsing failed:") + ex.what());
-        return (answer);
+        LOG_ERROR(dhcp6_logger, DHCP6_PARSER_CREATE_FAIL).arg(current_parser)
+                  .arg(ex.what());
+        answer =
+            isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+        // An error occured, so make sure that we restore original data.
+        rollback = true;
+
     } catch (...) {
         // for things like bad_cast in boost::lexical_cast
-        ConstElementPtr answer = isc::config::createAnswer(1,
-                                 string("Configuration parsing failed"));
+        LOG_ERROR(dhcp6_logger, DHCP6_PARSER_CREATE_EXCEPTION)
+                  .arg(current_parser);
+        answer =
+            isc::config::createAnswer(1, string("Configuration parsing failed"));
+        // An error occured, so make sure that we restore original data.
+        rollback = true;
     }
 
-    try {
-        BOOST_FOREACH(ParserPtr parser, parsers) {
-            parser->commit();
+    // So far so good, there was no parsing error so let's commit the
+    // configuration. This will add created subnets and option values into
+    // the server's configuration.
+    // This operation should be exception safe but let's make sure.
+    if (!rollback) {
+        try {
+            BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
+                parser->commit();
+            }
+        }
+        catch (const isc::Exception& ex) {
+            LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
+            answer =
+                isc::config::createAnswer(2, string("Configuration commit failed:") 
+                                          + ex.what());
+            // An error occured, so make sure to restore the original data.
+            rollback = true;
+        } catch (...) {
+            // for things like bad_cast in boost::lexical_cast
+            LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_EXCEPTION);
+            answer =
+                isc::config::createAnswer(2, string("Configuration commit failed"));
+            // An error occured, so make sure to restore the original data.
+            rollback = true;
         }
     }
-    catch (const isc::Exception& ex) {
-        ConstElementPtr answer = isc::config::createAnswer(2,
-                                 string("Configuration commit failed:") + ex.what());
+
+    // Rollback changes as the configuration parsing failed.
+    if (rollback) {
+        std::swap(uint32_defaults, uint32_local);
+        std::swap(string_defaults, string_local);
+        std::swap(option_defaults, option_local);
         return (answer);
-    } catch (...) {
-        // for things like bad_cast in boost::lexical_cast
-        ConstElementPtr answer = isc::config::createAnswer(2,
-                                 string("Configuration commit failed"));
     }
 
     LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
 
-    ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+    // Everything was fine. Configuration is successful.
+    answer = isc::config::createAnswer(0, "Configuration commited.");
     return (answer);
 }
 
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index ed44bb9..408d01f 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -28,7 +28,7 @@ namespace dhcp {
 class Dhcpv6Srv;
 
 /// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv6Srv object.
+/// @c Dhcpv6Srv object.
 class Dhcp6ConfigError : public isc::Exception {
 public:
 
@@ -41,115 +41,25 @@ public:
         : isc::Exception(file, line, what) {}
 };
 
-/// @brief Base abstract class for all DHCPv6 parsers
+/// @brief Configures DHCPv6 server
 ///
-/// Each instance of a class derived from this class parses one specific config
-/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
-/// complex (e.g. a subnet). In such case, it is likely that a parser will
-/// spawn child parsers to parse child elements in the configuration.
-/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
-class DhcpConfigParser {
-    ///
-    /// \name Constructors and Destructor
-    ///
-    /// Note: The copy constructor and the assignment operator are
-    /// intentionally defined as private to make it explicit that this is a
-    /// pure base class.
-    //@{
-private:
-    DhcpConfigParser(const DhcpConfigParser& source);
-    DhcpConfigParser& operator=(const DhcpConfigParser& source);
-protected:
-    /// \brief The default constructor.
-    ///
-    /// This is intentionally defined as \c protected as this base class should
-    /// never be instantiated (except as part of a derived class).
-    DhcpConfigParser() {}
-public:
-    /// The destructor.
-    virtual ~DhcpConfigParser() {}
-    //@}
-
-    /// \brief Prepare configuration value.
-    ///
-    /// This method parses the "value part" of the configuration identifier
-    /// that corresponds to this derived class and prepares a new value to
-    /// apply to the server.
-    ///
-    /// This method must validate the given value both in terms of syntax
-    /// and semantics of the configuration, so that the server will be
-    /// validly configured at the time of \c commit().  Note: the given
-    /// configuration value is normally syntactically validated, but the
-    /// \c build() implementation must also expect invalid input.  If it
-    /// detects an error it may throw an exception of a derived class
-    /// of \c isc::Exception.
-    ///
-    /// Preparing a configuration value will often require resource
-    /// allocation.  If it fails, it may throw a corresponding standard
-    /// exception.
-    ///
-    /// This method is not expected to be called more than once in the
-    /// life of the object. Although multiple calls are not prohibited
-    /// by the interface, the behavior is undefined.
-    ///
-    /// \param config_value The configuration value for the identifier
-    /// corresponding to the derived class.
-    virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
-    /// \brief Apply the prepared configuration value to the server.
-    ///
-    /// This method is expected to be exception free, and, as a consequence,
-    /// it should normally not involve resource allocation.
-    /// Typically it would simply perform exception free assignment or swap
-    /// operation on the value prepared in \c build().
-    /// In some cases, however, it may be very difficult to meet this
-    /// condition in a realistic way, while the failure case should really
-    /// be very rare.  In such a case it may throw, and, if the parser is
-    /// called via \c configureDhcp6Server(), the caller will convert the
-    /// exception as a fatal error.
-    ///
-    /// This method is expected to be called after \c build(), and only once.
-    /// The result is undefined otherwise.
-    virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
-///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
-/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
-/// that is, it ensures that either configuration is fully applied or the
-/// state of the server is intact.
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv6 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
 ///
-/// If a syntax or semantics level error happens during the configuration
-/// (such as malformed configuration or invalid configuration parameter),
-/// this function throws an exception of class \c Dhcp6ConfigError.
-/// If the given configuration requires resource allocation and it fails,
-/// a corresponding standard exception will be thrown.
-/// Other exceptions may also be thrown, depending on the implementation of
-/// the underlying derived class of \c Dhcp6ConfigError.
-/// In any case the strong guarantee is provided as described above except
-/// in the very rare cases where the \c commit() method of a parser throws
-/// an exception.  If that happens this function converts the exception
-/// into a \c FatalError exception and rethrows it.  This exception is
-/// expected to be caught at the highest level of the application to terminate
-/// the program gracefully.
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - commit failed (parsing was successful, but the values could not be
+/// stored in the configuration).
 ///
-/// \param server The \c Dhcpv6Srv object to be configured.
-/// \param config_set A JSON style configuration to apply to \c server.
+/// @param server DHCPv6 server object.
+/// @param config_set a new configuration for DHCPv6 server.
+/// @return answer that contains result of the reconfiguration.
+/// @throw Dhcp6ConfigError if trying to create a parser for NULL config.
 isc::data::ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& server,
-                     isc::data::ConstElementPtr config_set);
+configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
diff --git a/src/bin/dhcp6/dbaccess_parser.cc b/src/bin/dhcp6/dbaccess_parser.cc
deleted file mode 100644
index 9743380..0000000
--- a/src/bin/dhcp6/dbaccess_parser.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <dhcp6/dbaccess_parser.h>
-
-#include <boost/foreach.hpp>
-
-#include <map>
-#include <string>
-#include <utility>
-
-using namespace std;
-using namespace isc::data;
-
-namespace isc {
-namespace dhcp {
-
-typedef map<string, ConstElementPtr> ConfigPairMap;
-typedef pair<string, ConstElementPtr> ConfigPair;
-typedef map<string, string> StringPairMap;
-typedef pair<string, string> StringPair;
-
-// Parse the configuration and check that the various keywords are consistent.
-void
-DbAccessParser::build(isc::data::ConstElementPtr config_value) {
-    const ConfigPairMap& config_map = config_value->mapValue();
-
-    // To cope with incremental updates, the strategy is:
-    // 1. Take a copy of the stored keyword/value pairs.
-    // 2. Update the copy with the passed keywords.
-    // 3. Perform validation checks on the updated keyword/value pairs.
-    // 4. If all is OK, update the stored keyword/value pairs.
-    // 5. Construct the updated database access string.
-
-    // 1. Take a copy of the stored keyword/value pairs.
-    map<string, string> values_copy = values_;
-
-    // 2. Update the copy with the passed keywords.
-    BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
-        values_copy[param.first] = param.second->stringValue();
-    }
-
-    // 3. Perform validation checks on the updated set of keyword/values.
-    //
-    // a. Check if the "type" keyword exists and thrown an exception if not.
-    StringPairMap::const_iterator type_ptr = values_copy.find("type");
-    if (type_ptr == values_copy.end()) {
-        isc_throw(TypeKeywordMissing, "lease database access parameters must "
-                  "include the keyword 'type' to determine type of database "
-                  "to be accessed");
-    }
-
-    // b. Check if the 'type; keyword known and throw an exception if not.
-    string dbtype = type_ptr->second;
-    if ((dbtype != "memfile") && (dbtype != "mysql")) {
-        isc_throw(BadValue, "unknown backend database type: " << dbtype);
-    }
-
-    /// @todo Log a warning if the type is memfile and there are other keywords.
-    ///       This will be done when the module is moved to libdhcpsrv
-
-    // 4. If all is OK, update the stored keyword/value pairs.
-    values_ = values_copy;
-
-    // 5. Construct the updated database access string.
-    dbaccess_ = "";
-    BOOST_FOREACH(StringPair keyval, values_) {
-        if (! dbaccess_.empty()) {
-            dbaccess_ += std::string(" ");
-        }
-        dbaccess_ += (keyval.first + std::string("=") + keyval.second);
-    }
-}
-
-// Commit the changes - reopen the database with the new parameters
-void
-DbAccessParser::commit() {
-    std::cout << "DB_ACCESS_PARSER_COMMIT: " << dbaccess_ << "\n";
-}
-
-};  // namespace dhcp
-};  // namespace isc
-
diff --git a/src/bin/dhcp6/dbaccess_parser.h b/src/bin/dhcp6/dbaccess_parser.h
deleted file mode 100644
index fe44ad6..0000000
--- a/src/bin/dhcp6/dbaccess_parser.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef DBACCESS_PARSER_H
-#define DBACCESS_PARSER_H
-
-#include <cc/data.h>
-#include <dhcp6/config_parser.h>
-#include <exceptions/exceptions.h>
-
-#include <string>
-
-namespace isc {
-namespace dhcp {
-
-/// @brief Exception thrown when 'type' keyword is missing from string
-///
-/// This condition is checked, but should never occur because 'type' is marked
-/// as mandatory in the .spec file for the server.
-class TypeKeywordMissing : public isc::Exception {
-public:
-    TypeKeywordMissing(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-/// @brief Parse Lease Database Parameters
-///
-/// This class is the parser for the lease database configuration.  This is a
-/// map under the top-level "lease-database" element, and comprises a map of
-/// strings.
-///
-/// Only the "type" sub-element is mandatory: the remaining sub-elements 
-/// depend on the datbase chosen.
-class DbAccessParser: public DhcpConfigParser {
-public:
-
-    /// @brief Default constructor
-    ///
-    /// @param param_name Name of the configuration parameter being parsed.
-    DbAccessParser(const std::string& param_name)
-        : dbaccess_(), param_name_(param_name), values_()
-    {}
-
-    /// The destructor.
-    virtual ~DbAccessParser()
-    {}
-
-    /// @brief Prepare configuration value.
-    ///
-    /// Parses the set of strings forming the database access specification and
-    /// checks that all are OK.  In particular it checks:
-    ///
-    /// - "type" is "memfile" or "mysql"
-    /// - If "type" is "memfile", checks that no other values are present: if
-    ///   they are, logs a warning that they will be ignored.
-    ///
-    /// Once all has been validated, constructs the database access string
-    /// expected by the lease manager.
-    ///
-    /// @param config_value The configuration value for the "lease-database"
-    ///        identifier.
-    ///
-    /// @throw isc::BadValue The 'type' keyword contains an unknown database
-    ///        type.
-    /// @throw isc::dhcp::MissingTypeKeyword The 'type' keyword is missing from
-    ///        the list of database access keywords.
-    virtual void build(isc::data::ConstElementPtr config_value);
-
-    /// @brief Apply the prepared configuration value to the server.
-    ///
-    /// With the string validated, this closes the currently open database (if
-    /// any), then opens a database corresponding to the stored string.
-    ///
-    /// This method is expected to be called after \c build(), and only once.
-    /// The result is undefined otherwise.
-    virtual void commit();
-
-    /// @brief Factory method to create parser
-    ///
-    /// Creates an instance of this parser.
-    static DhcpConfigParser* factory(const std::string& param_name) {
-        return (new DbAccessParser(param_name));
-    }
-
-    /// @brief Get database access string
-    ///
-    /// Used in testing to check that the configuration information has been
-    /// parsed corrected.
-    std::string getDbAccessString() const {
-        return (dbaccess_);
-    }
-
-private:
-    std::string     dbaccess_;      ///< Database access string
-    std::string     param_name_;    ///< Parameter name
-    std::map<std::string, std::string> values_;
-                                    ///< Stored parameter values
-};
-
-};  // namespace dhcp
-};  // namespace isc
-
-
-#endif // DBACCESS_PARSER_H
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index 5350fd8..fa37769 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -35,19 +35,19 @@
 
  This method iterates over list of received configuration elements and creates a
  list of parsers for each received entry. Parser is an object that is derived
- from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
+ from a DhcpConfigParser class. Once a parser is created
  (constructor), its value is set (using build() method). Once all parsers are
  build, the configuration is then applied ("commited") and commit() method is
  called.
 
  All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
- are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
- unsigned 32 bit integer), but some are very specialized (e.g. \ref
- isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
- some cases, e.g. subnet6 definitions, the configuration entry is not a simple
- value, but a map or a list itself. In such case, the parser iterates over all
- elements and creates parsers for a given scope. This process may be repeated
- (sort of) recursively.
+ are generic (e.g. Uint32Parser that is able to handle any
+ unsigned 32 bit integer), but some are very specialized (e.g.
+ Subnets6ListConfigParser parses definitions of Subnet6 lists). In some cases,
+ e.g. subnet6 definitions, the configuration entry is not a simple value, but
+ a map or a list itself. In such case, the parser iterates over all elements
+ and creates parsers for a given scope. This process may be repeated (sort of)
+ recursively.
 
  @section dhcpv6ConfigInherit DHCPv6 Configuration Inheritance
 
@@ -55,16 +55,16 @@
  For example, renew-timer value may be specified at a global scope and it then
  applies to all subnets. However, some subnets may have it overwritten with more
  specific values that takes precedence over global values that are considered
- defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
- isc::dhcp::StringParser) implement that inheritance. By default, they store
- values in global uint32_defaults and string_defaults storages. However, it is
- possible to instruct them to store parsed values in more specific
- storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
- that has its own storage that is unique for each subnet. Finally, during commit
- phase (commit() method), appropriate parsers can use apply parameter inheritance.
+ defaults. Some parsers (e.g. Uint32Parser and StringParser) implement that
+ inheritance. By default, they store values in global uint32_defaults and
+ string_defaults storages. However, it is possible to instruct them to store
+ parsed values in more specific storages. That capability is used, e.g. in
+ Subnet6ConfigParser that has its own storage that is unique for each subnet.
+ Finally, during commit phase (commit() method), appropriate parsers can use
+ apply parameter inheritance.
 
  Debugging configuration parser may be confusing. Therefore there is a special
- class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
+ class called DebugParser. It does not configure anything, but just
  accepts any parameter of any type. If requested to commit configuration, it will
  print out received parameter name and its value. This class is not currently used,
  but it is convenient to have it every time a new parameter is added to DHCP
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 3cd4525..b65bfbd 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -67,6 +67,11 @@
             "item_type": "string",
             "item_optional": false,
             "item_default": ""
+          },
+          { "item_name": "csv-format",
+            "item_type": "boolean",
+            "item_optional": false,
+            "item_default": False
           } ]
         }
       },
@@ -190,6 +195,11 @@
                       "item_type": "string",
                       "item_optional": false,
                       "item_default": ""
+                    },
+                    { "item_name": "csv-format",
+                      "item_type": "boolean",
+                      "item_optional": false,
+                      "item_default": False
                     } ]
                   }
                 } ]
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6ab42b3..f80af12 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -47,9 +47,9 @@ This is an informational message reporting that the configuration has
 been extended to include the specified subnet.
 
 % DHCP6_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
-This warning message is issued on attempt to configure multiple options with the
+This warning message is issued on an attempt to configure multiple options with the
 same option code for the particular subnet. Adding multiple options is uncommon
-for DHCPv6, yet it is not prohibited.
+for DHCPv6, but it is not prohibited.
 
 % DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1
 This is a debug message that is issued every time the server receives a
@@ -65,11 +65,18 @@ This informational message is printed every time DHCPv6 is started.
 It indicates what database backend type is being to store lease and
 other information.
 
+% DHCP6_LEASE_WITHOUT_DUID lease for address %1 does not have a DUID
+This error message indicates a database consistency failure. The lease
+database has an entry indicating that the given address is in use,
+but the lease does not contain any client identification. This is most
+likely due to a software error: please raise a bug report. As a temporary
+workaround, manually remove the lease entry from the database.
+
 % DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
 This debug message indicates that the server successfully advertised
-a lease. It is up to the client to choose one server out of othe advertised
-and continue allocation with that server. This is a normal behavior and
-indicates successful operation.
+a lease. It is up to the client to choose one server out of the
+advertised servers and continue allocation with that server. This
+is a normal behavior and indicates successful operation.
 
 % DHCP6_LEASE_ADVERT_FAIL failed to advertise a lease for client duid=%1, iaid=%2
 This message indicates that the server failed to advertise (in response to
@@ -79,19 +86,43 @@ such failure. Each specific failure is logged in a separate log entry.
 % DHCP6_LEASE_ALLOC lease %1 has been allocated (client duid=%2, iaid=%3)
 This debug message indicates that the server successfully granted (in
 response to client's REQUEST message) a lease. This is a normal behavior
-and incicates successful operation.
+and indicates successful operation.
 
 % DHCP6_LEASE_ALLOC_FAIL failed to grant a lease for client duid=%1, iaid=%2
 This message indicates that the server failed to grant (in response to
 received REQUEST) a lease for a given client. There may be many reasons for
 such failure. Each specific failure is logged in a separate log entry.
 
-% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
-This message indicates that received DHCPv6 packet is invalid.  This may be due
-to a number of reasons, e.g. the mandatory client-id option is missing,
-the server-id forbidden in that particular type of message is present,
-there is more than one instance of client-id or server-id present,
-etc. The exact reason for rejecting the packet is included in the message.
+% DHCP6_RELEASE address %1 belonging to client duid=%2, iaid=%3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP6_RELEASE_FAIL failed to remove lease for address %1 for duid=%2, iaid=%3
+This error message indicates that the software failed to remove a
+lease from the lease database.  It probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP6_RELEASE_FAIL_WRONG_DUID client (duid=%1) tried to release address %2, but it belongs to client (duid=%3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client.  However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP6_RELEASE_FAIL_WRONG_IAID client (duid=%1) tried to release address %2, but it used wrong IAID (expected %3, but got %4)
+This warning message indicates that client tried to release an address
+that does belong to it, but the address was expected to be in a different
+IA (identity association) container. This probably means that the client's
+support for multiple addresses is flawed.
+
+% DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id
+This warning message indicates that client sent RELEASE message without
+mandatory client-id option. This is most likely caused by a buggy client
+(or a relay that malformed forwarded message). This request will not be
+processed and a response with error status code will be sent back.
 
 % DHCP6_NOT_RUNNING IPv6 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
@@ -128,7 +159,7 @@ a received OFFER packet as UNKNOWN).
 
 % DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
 The IPv6 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
 the message.
 
 % DHCP6_PACKET_SEND_FAIL failed to send DHCPv6 packet: %1
@@ -141,6 +172,46 @@ This error is output if the server failed to assemble the data to be
 returned to the client into a valid packet.  The reason is most likely
 to be to a programming error: please raise a bug report.
 
+% DHCP6_PARSER_COMMIT_EXCEPTION parser failed to commit changes
+On receipt of message containing details to a change of the IPv6 DHCP
+server configuration, a set of parsers were successfully created, but one
+of them failed to commit its changes due to a low-level system exception
+being raised.  Additional messages may be output indicating the reason.
+
+% DHCP6_PARSER_COMMIT_FAIL parser failed to commit changes: %1
+On receipt of message containing details to a change of the IPv6 DHCP
+server configuration, a set of parsers were successfully created, but
+one of them failed to commit its changes.  The reason for the failure
+is given in the message.
+
+% DHCP6_PARSER_CREATED1 created parser for configuration element %1
+A debug message output during a configuration update of the IPv6 DHCP
+server, notifying that the parser for the specified configuration element
+has been successfully created in the first pass through creating parsers
+
+% DHCP6_PARSER_CREATED2 created parser for configuration element %1
+A debug message output during a configuration update of the IPv6 DHCP
+server, notifying that the parser for the specified configuration element
+has been successfully created in the second pass through creating parsers
+
+% DHCP6_PARSER_CREATE_FAIL failed to create parser for configuration element %1: %2
+On receipt of message containing details to a change of its configuration,
+the IPv6 DHCP server failed to create a parser to decode the contents of the
+named configuration element.  The reason for the failure is given in the
+message.
+
+% DHCP6_PARSER_CREATE_EXCEPTION failed to create parser for configuration element %1
+On receipt of message containing details to a change of its configuration,
+the IPv6 DHCP server failed to create a parser to decode the contents
+of the named configuration element.  The message has been output in
+response to a non-BIND 10 exception being raised.  Additional messages
+may give further information.
+
+The most likely cause of this is that the specification file for the server
+(which details the allowable contents of the configuration) is not correct for
+this version of BIND 10.  This former may be the result of an interrupted
+installation of an update to BIND 10.
+
 % DHCP6_PROCESS_IA_NA_REQUEST server is processing IA_NA option (duid=%1, iaid=%2, hint=%3)
 This is a debug message that indicates a processing of received IA_NA
 option. It may optionally contain an address that may be used by the server
@@ -149,6 +220,13 @@ as a hint for possible requested address.
 % DHCP6_QUERY_DATA received packet length %1, data length %2, data is %3
 A debug message listing the data received from the client or relay.
 
+% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
+This message indicates that received DHCPv6 packet is invalid.  This may be due
+to a number of reasons, e.g. the mandatory client-id option is missing,
+the server-id forbidden in that particular type of message is present,
+there is more than one instance of client-id or server-id present,
+etc. The exact reason for rejecting the packet is included in the message.
+
 % DHCP6_RESPONSE_DATA responding with packet type %1 data is %2
 A debug message listing the data returned to the client.
 
@@ -216,3 +294,8 @@ recently and does not recognize its well-behaving clients. This is more
 probable if you see many such messages. Clients will recover from this,
 but they will most likely get a different IP addresses and experience
 a brief service interruption.
+
+% DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
+This warning message is printed when client attempts to release a lease,
+but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for
+possible reasons for such behavior.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index a50be03..b90c33e 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 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
@@ -340,8 +340,8 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
     // Get the list of options that client requested.
     const std::vector<uint16_t>& requested_opts = option_oro->getValues();
     // Get the list of options configured for a subnet.
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
     // Try to match requested options with those configured for a subnet.
     // If match is found, append configured option to the answer message.
     BOOST_FOREACH(uint16_t opt, requested_opts) {
@@ -436,6 +436,8 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
 
     // We need to allocate addresses for all IA_NA options in the client's
     // question (i.e. SOLICIT or REQUEST) message.
+    // @todo add support for IA_TA
+    // @todo add support for IA_PD
 
     // We need to select a subnet the client is connected in.
     Subnet6Ptr subnet = selectSubnet(question);
@@ -604,7 +606,7 @@ OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
 
         // Insert status code NoAddrsAvail.
-        ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
                           "Sorry, no known leases for this duid/iaid."));
 
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
@@ -640,6 +642,8 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
 
     // We need to renew addresses for all IA_NA options in the client's
     // RENEW message.
+    // @todo add support for IA_TA
+    // @todo add support for IA_PD
 
     // We need to select a subnet the client is connected in.
     Subnet6Ptr subnet = selectSubnet(renew);
@@ -688,11 +692,176 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
             break;
         }
     }
+}
+
+void Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
+
+    // We need to release addresses for all IA_NA options in the client's
+    // RELEASE message.
+    // @todo Add support for IA_TA
+    // @todo Add support for IA_PD
+    // @todo Consider supporting more than one address in a single IA_NA.
+    // That was envisaged by RFC3315, but it never happened. The only
+    // software that supports that is Dibbler, but its author seriously doubts
+    // if anyone is really using it. Clients that want more than one address
+    // just include more instances of IA_NA options.
+
+    // Let's find client's DUID. Client is supposed to include its client-id
+    // option almost all the time (the only exception is an anonymous inf-request,
+    // but that is mostly a theoretical case). Our allocation engine needs DUID
+    // and will refuse to allocate anything to anonymous clients.
+    OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
+    if (!opt_duid) {
+        // This should not happen. We have checked this before.
+        // see sanityCheck() called from processRelease()
+        LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
+            .arg(release->getRemoteAddr().toText());
+
+        reply->addOption(createStatusCode(STATUS_UnspecFail,
+                         "You did not include mandatory client-id"));
+        return;
+    }
+    DuidPtr duid(new DUID(opt_duid->getData()));
+
+    int general_status = STATUS_Success;
+    for (Option::OptionCollection::iterator opt = release->options_.begin();
+         opt != release->options_.end(); ++opt) {
+        switch (opt->second->getType()) {
+        case D6O_IA_NA: {
+            OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
+                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
+            if (answer_opt) {
+                reply->addOption(answer_opt);
+            }
+            break;
+        }
+        // @todo: add support for IA_PD
+        // @todo: add support for IA_TA
+        default:
+            // remaining options are stateless and thus ignored in this context
+            ;
+        }
+    }
+
+    // To be pedantic, we should also include status code in the top-level
+    // scope, not just in each IA_NA. See RFC3315, section 18.2.6.
+    // This behavior will likely go away in RFC3315bis.
+    reply->addOption(createStatusCode(general_status,
+                     "Summary status for all processed IA_NAs"));
+}
+
+OptionPtr Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+                                  int& general_status,
+                                  boost::shared_ptr<Option6IA> ia) {
+    // Release can be done in one of two ways:
+    // Approach 1: extract address from client's IA_NA and see if it belongs
+    // to this particular client.
+    // Approach 2: find a subnet for this client, get a lease for
+    // this subnet/duid/iaid and check if its content matches to what the
+    // client is asking us to release.
+    //
+    // This method implements approach 1.
+
+    // That's our response
+    boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+    boost::shared_ptr<Option6IAAddr> release_addr = boost::dynamic_pointer_cast<Option6IAAddr>
+        (ia->getOption(D6O_IAADDR));
+    if (!release_addr) {
+        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                                           "You did not include address in your RELEASE"));
+        general_status = STATUS_NoBinding;
+        return (ia_rsp);
+    }
+
+    Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(release_addr->getAddress());
+
+    if (!lease) {
+        // client releasing a lease that we don't know about.
+
+        // Insert status code NoAddrsAvail.
+        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                          "Sorry, no known leases for this duid/iaid, can't release."));
+        general_status = STATUS_NoBinding;
+
+        LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE)
+            .arg(duid->toText())
+            .arg(ia->getIAID());
+
+        return (ia_rsp);
+    }
+
+    if (!lease->duid_) {
+        // Something is gravely wrong here. We do have a lease, but it does not
+        // have mandatory DUID information attached. Someone was messing with our
+        // database.
+
+        LOG_ERROR(dhcp6_logger, DHCP6_LEASE_WITHOUT_DUID)
+            .arg(release_addr->getAddress().toText());
+
+        general_status = STATUS_UnspecFail;
+        ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+                          "Database consistency check failed when trying to RELEASE"));
+        return (ia_rsp);
+    }
+
+    if (*duid != *(lease->duid_)) {
+        // Sorry, it's not your address. You can't release it.
+
+        LOG_INFO(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_DUID)
+            .arg(duid->toText())
+            .arg(release_addr->getAddress().toText())
+            .arg(lease->duid_->toText());
+
+        general_status = STATUS_NoBinding;
+        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                          "This address does not belong to you, you can't release it"));
+        return (ia_rsp);
+    }
+
+    if (ia->getIAID() != lease->iaid_) {
+        // This address belongs to this client, but to a different IA
+        LOG_WARN(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_IAID)
+            .arg(duid->toText())
+            .arg(release_addr->getAddress().toText())
+            .arg(lease->iaid_)
+            .arg(ia->getIAID());
+        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                          "This is your address, but you used wrong IAID"));
+        general_status = STATUS_NoBinding;
+        return (ia_rsp);
+    }
+
+    // It is not necessary to check if the address matches as we used
+    // getLease6(addr) method that is supposed to return a proper lease.
 
+    // Ok, we've passed all checks. Let's release this address.
 
+    if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+        ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+                          "Server failed to release a lease"));
+
+        LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_FAIL)
+            .arg(lease->addr_.toText())
+            .arg(duid->toText())
+            .arg(lease->iaid_);
+        general_status = STATUS_UnspecFail;
+
+        return (ia_rsp);
+    } else {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE)
+            .arg(lease->addr_.toText())
+            .arg(duid->toText())
+            .arg(lease->iaid_);
 
+        ia_rsp->addOption(createStatusCode(STATUS_Success,
+                          "Lease released. Thank you, please come again."));
+
+        return (ia_rsp);
+    }
 }
 
+
 Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
 
     sanityCheck(solicit, MANDATORY, FORBIDDEN);
@@ -751,8 +920,16 @@ Pkt6Ptr Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
 }
 
 Pkt6Ptr Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
-    /// @todo: Implement this
+
+    sanityCheck(release, MANDATORY, MANDATORY);
+
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
+
+    copyDefaultOptions(release, reply);
+    appendDefaultOptions(release, reply);
+
+    releaseLeases(release, reply);
+
     return reply;
 }
 
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index b6b0306..0f3e641 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -212,17 +212,39 @@ protected:
 
     /// @brief Renews specific IA_NA option
     ///
-    /// Generates response to IA_NA. This typically includes finding a lease that
-    /// corresponds to the received address. If no such lease is found, an IA_NA
-    /// response is generated with an appropriate status code.
+    /// Generates response to IA_NA in Renew. This typically includes finding a
+    /// lease that corresponds to the received address. If no such lease is
+    /// found, an IA_NA response is generated with an appropriate status code.
     ///
     /// @param subnet subnet the sender belongs to
     /// @param duid client's duid
     /// @param question client's message
     /// @param ia IA_NA option that is being renewed
+    /// @return IA_NA option (server's response)
     OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                          Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
 
+    /// @brief Releases specific IA_NA option
+    ///
+    /// Generates response to IA_NA in Release message. This covers finding and
+    /// removal of a lease that corresponds to the received address. If no such
+    /// lease is found, an IA_NA response is generated with an appropriate
+    /// status code.
+    ///
+    /// As RFC 3315 requires that a single status code be sent for the whole message,
+    /// this method may update the passed general_status: it is set to SUCCESS when
+    /// message processing begins, but may be updated to some error code if the
+    /// release process fails.
+    ///
+    /// @param duid client's duid
+    /// @param question client's message
+    /// @param general_status a global status (it may be updated in case of errors)
+    /// @param ia IA_NA option that is being renewed
+    /// @return IA_NA option (server's response)
+    OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+                           int& general_status,
+                           boost::shared_ptr<Option6IA> ia);
+
     /// @brief Copies required options from client message to server answer.
     ///
     /// Copies options that must appear in any server response (ADVERTISE, REPLY)
@@ -271,6 +293,17 @@ protected:
     /// @param reply server's response
     void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
 
+    /// @brief Attempts to release received addresses
+    ///
+    /// It iterates through received IA_NA options and attempts to release
+    /// received addresses. If no such leases are found, or the lease fails
+    /// proper checks (e.g. belongs to someone else), a proper status
+    /// code is added to reply message. Released addresses are not added
+    /// to REPLY packet, just its IA_NA containers.
+    /// @param release client's message asking to release
+    /// @param reply server's response
+    void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply);
+
     /// @brief Sets server-identifier.
     ///
     /// This method attempts to set server-identifier DUID. It loads it
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index d84cf57..d251df3 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -47,10 +47,8 @@ dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
-dhcp6_unittests_SOURCES += dbaccess_parser_unittest.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
-dhcp6_unittests_SOURCES += ../dbaccess_parser.h ../dbaccess_parser.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
 nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
@@ -65,14 +63,13 @@ dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
-
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 endif
 
 noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 4fd6baa..be5bbf8 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -56,7 +56,8 @@ public:
     /// @brief Create the simple configuration with single option.
     ///
     /// This function allows to set one of the parameters that configure
-    /// option value. These parameters are: "name", "code" and "data".
+    /// option value. These parameters are: "name", "code", "data" and
+    /// "csv-format".
     ///
     /// @param param_value string holiding option parameter value to be
     /// injected into the configuration string.
@@ -69,19 +70,29 @@ public:
             params["name"] = param_value;
             params["code"] = "80";
             params["data"] = "AB CDEF0105";
+            params["csv-format"] = "False";
         } else if (parameter == "code") {
             params["name"] = "option_foo";
             params["code"] = param_value;
             params["data"] = "AB CDEF0105";
+            params["csv-format"] = "False";
         } else if (parameter == "data") {
             params["name"] = "option_foo";
             params["code"] = "80";
             params["data"] = param_value;
+            params["csv-format"] = "False";
+        } else if (parameter == "csv-format") {
+            params["name"] = "option_foo";
+            params["code"] = "80";
+            params["data"] = "AB CDEF0105";
+            params["csv-format"] = param_value;
         }
         return (createConfigWithOption(params));
     }
 
-    std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
+    std::string createConfigWithOption(const std::map<std::string,
+                                       std::string>& params)
+    {
         std::ostringstream stream;
         stream << "{ \"interface\": [ \"all\" ],"
             "\"preferred-lifetime\": 3000,"
@@ -97,14 +108,17 @@ public:
             if (!first) {
                 stream << ", ";
             } else {
+                // cppcheck-suppress unreadVariable
                 first = false;
             }
             if (param.first == "name") {
                 stream << "\"name\": \"" << param.second << "\"";
             } else if (param.first == "code") {
-                stream << "\"code\": " << param.second << "";
+                stream << "\"code\": " << param.second;;
             } else if (param.first == "data") {
                 stream << "\"data\": \"" << param.second << "\"";
+            } else if (param.first == "csv-format") {
+                stream << "\"csv-format\": " << param.second;
             }
         }
         stream <<
@@ -144,14 +158,14 @@ public:
                    << ex.what() << std::endl;
         }
 
-
-        // returned value should be 0 (configuration success)
+        // status object must not be NULL
         if (!status) {
             FAIL() << "Fatal error: unable to reset configuration database"
                    << " after the test. Configuration function returned"
                    << " NULL pointer" << std::endl;
         }
         comment_ = parseAnswer(rcode_, status);
+        // returned value should be 0 (configuration success)
         if (rcode_ != 0) {
             FAIL() << "Fatal error: unable to reset configuration database"
                    << " after the test. Configuration function returned"
@@ -215,9 +229,10 @@ public:
             ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
                       expected_data_len);
         }
-        // Verify that the data is correct. However do not verify suboptions.
+        // Verify that the data is correct. Do not verify suboptions and a header.
         const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
-        EXPECT_TRUE(memcmp(expected_data, data, expected_data_len));
+        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+                            expected_data_len));
     }
 
     Dhcpv6Srv srv_;
@@ -370,11 +385,12 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
 
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
 
-    // returned value must be 2 (values error)
+    // returned value must be 1 (values error)
     // as the pool does not belong to that subnet
     ASSERT_TRUE(status);
     comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(2, rcode_);
+
+    EXPECT_EQ(1, rcode_);
 }
 
 // Goal of this test is to verify if pools can be defined
@@ -423,12 +439,14 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
         "\"option-data\": [ {"
         "    \"name\": \"option_foo\","
         "    \"code\": 100,"
-        "    \"data\": \"AB CDEF0105\""
+        "    \"data\": \"AB CDEF0105\","
+        "    \"csv-format\": False"
         " },"
         " {"
         "    \"name\": \"option_foo2\","
         "    \"code\": 101,"
-        "    \"data\": \"01\""
+        "    \"data\": \"01\","
+        "    \"csv-format\": False"
         " } ],"
         "\"subnet6\": [ { "
         "    \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -445,11 +463,11 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(2, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
@@ -493,7 +511,8 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
         "\"option-data\": [ {"
         "      \"name\": \"option_foo\","
         "      \"code\": 100,"
-        "      \"data\": \"AB\""
+        "      \"data\": \"AB\","
+        "      \"csv-format\": False"
         " } ],"
         "\"subnet6\": [ { "
         "    \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -501,12 +520,14 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
         "    \"option-data\": [ {"
         "          \"name\": \"option_foo\","
         "          \"code\": 100,"
-        "          \"data\": \"AB CDEF0105\""
+        "          \"data\": \"AB CDEF0105\","
+        "          \"csv-format\": False"
         "        },"
         "        {"
         "          \"name\": \"option_foo2\","
         "          \"code\": 101,"
-        "          \"data\": \"01\""
+        "          \"data\": \"01\","
+        "          \"csv-format\": False"
         "        } ]"
         " } ],"
         "\"valid-lifetime\": 4000 }";
@@ -520,11 +541,11 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(2, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(2, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
@@ -563,7 +584,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
         "    \"option-data\": [ {"
         "          \"name\": \"option_foo\","
         "          \"code\": 100,"
-        "          \"data\": \"0102030405060708090A\""
+        "          \"data\": \"0102030405060708090A\","
+        "          \"csv-format\": False"
         "        } ]"
         " },"
         " {"
@@ -572,7 +594,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
         "    \"option-data\": [ {"
         "          \"name\": \"option_foo2\","
         "          \"code\": 101,"
-        "          \"data\": \"FFFEFDFCFB\""
+        "          \"data\": \"FFFEFDFCFB\","
+        "          \"csv-format\": False"
         "        } ]"
         " } ],"
         "\"valid-lifetime\": 4000 }";
@@ -586,11 +609,11 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
 
     Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet1);
-    const Subnet::OptionContainer& options1 = subnet1->getOptions();
-    ASSERT_EQ(1, options1.size());
+    Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(1, options1->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
@@ -610,10 +633,10 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     // Test another subnet in the same way.
     Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
     ASSERT_TRUE(subnet2);
-    const Subnet::OptionContainer& options2 = subnet2->getOptions();
-    ASSERT_EQ(1, options2.size());
+    Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(1, options2->size());
 
-    const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
     std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
               Subnet::OptionContainerTypeIndex::const_iterator> range2 =
         idx2.equal_range(101);
@@ -704,11 +727,11 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(1, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(1, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
@@ -733,7 +756,8 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     params["name"] = "OPTION_IA_NA";
     // Option code 3 means OPTION_IA_NA.
     params["code"] = "3";
-    params["data"] = "ABCDEF01 02030405 06070809";
+    params["data"] = "12345, 6789, 1516";
+    params["csv-format"] = "True";
 
     std::string config = createConfigWithOption(params);
     ElementPtr json = Element::fromJSON(config);
@@ -745,11 +769,11 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    const Subnet::OptionContainer& options = subnet->getOptions();
-    ASSERT_EQ(1, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(1, options->size());
 
     // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
     // Get the options for specified index. Expecting one option to be
     // returned but in theory we may have multiple options with the same
@@ -774,9 +798,9 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     // If cast was successful we may use accessors exposed by
     // Option6IA to validate that the content of this option
     // has been set correctly.
-    EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
-    EXPECT_EQ(0x02030405, optionIA->getT1());
-    EXPECT_EQ(0x06070809, optionIA->getT2());
+    EXPECT_EQ(12345, optionIA->getIAID());
+    EXPECT_EQ(6789, optionIA->getT1());
+    EXPECT_EQ(1516, optionIA->getT2());
 }
 
 };
diff --git a/src/bin/dhcp6/tests/dbaccess_parser_unittest.cc b/src/bin/dhcp6/tests/dbaccess_parser_unittest.cc
deleted file mode 100644
index 291b8d4..0000000
--- a/src/bin/dhcp6/tests/dbaccess_parser_unittest.cc
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <config.h>
-
-#include <dhcp6/dbaccess_parser.h>
-#include <dhcpsrv/lease_mgr_factory.h>
-#include <config/ccsession.h>
-#include <gtest/gtest.h>
-
-#include <fstream>
-#include <iostream>
-#include <map>
-#include <sstream>
-#include <string>
-
-#include <arpa/inet.h>
-
-using namespace std;
-using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::data;
-using namespace isc::config;
-
-namespace {
-
-/// @brief Database Access Parser test fixture class
-class DbAccessParserTest : public ::testing::Test {
-public:
-    /// @ Build JSON String
-    ///
-    /// Given a array of "const char*" strings representing in order, keyword,
-    /// value, keyword, value, ... and terminated by a NULL, return a string
-    /// that represents the JSON map for the keywords and values.
-    ///
-    /// E.g. given the array of strings: alpha, one, beta, two, NULL, it would
-    /// return the string '{ "alpha": "one", "beta": "two" }'
-    ///
-    /// @param keyval Array of "const char*" strings in the order keyword,
-    ///        value, keyword, value ...  A NULL entry terminates the list.
-    ///
-    /// @return JSON map for the keyword value array.
-    std::string toJson(const char* keyval[]) {
-        const std::string quote = "\"";
-        const std::string colon = ":";
-        const std::string space = " ";
-
-        string result = "{ ";
-
-        for (size_t i = 0; keyval[i] != NULL; i+= 2) {
-            // Get the value.  This should not be NULL.  As ASSERT_NE will
-            // cause a return - which gives compilation problems as a return
-            // statement is expected to return a string - use EXPECT_NE and
-            // explicitly return if the expected array is incorrect.
-            EXPECT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
-                "Supplied reference keyword/value list does not contain values "
-                "for all keywords";
-            if (keyval[i + 1] == NULL) {
-                return (std::string(""));
-            }
-
-            // Add the separating comma if not the first.
-            if (i != 0) {
-                result += ", ";
-            }
-
-            // Add the keyword and value - make sure that they are quoted.
-            result += quote + keyval[i] + quote + colon + space +
-                      quote + keyval[i + 1] + quote;
-        }
-
-        // Add the terminating brace
-        result += " }";
-
-        return (result);
-    }
-
-    /// @brief Check for Keywords
-    ///
-    /// Takes a database access string and checks it against a list of keywords
-    /// and values.  It checks that:
-    ///
-    /// a. Every keyword in the string appears once and only once in the
-    ///    list.
-    /// b. Every keyword in the list appears in the string.
-    /// c. Every keyword's value is the same as that in the string.
-    ///
-    /// To parse the access string, we use the parsing function in the
-    /// DHCP lease manager.
-    ///
-    /// @param trace_string String that will be used to set the value of a
-    ///        SCOPED_TRACE for this call.
-    /// @param dbaccess Database access string to check
-    /// @param keyval Array of "const char*" strings in the order keyword,
-    ///        value, keyword, value ...  A NULL entry terminates the list.
-    void checkAccessString(const char* trace_string, std::string& dbaccess,
-                           const char* keyval[]) {
-        SCOPED_TRACE(trace_string);
-
-        // Construct a map of keyword value pairs.  Check that no keyword
-        // is repeated.
-        map<string, string> expected;
-        size_t expected_count = 0;
-        for (size_t i = 0; keyval[i] != NULL; i += 2) {
-            // Get the value.  This should not be NULL
-            ASSERT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
-                "Supplied reference keyword/value list does not contain values "
-                "for all keywords";
-            expected[keyval[i]] = keyval[i + 1];
-
-            // One more keyword processed
-            ++expected_count;
-        }
-
-        // Check no duplicates in the supplied keywords
-        ASSERT_EQ(expected_count, expected.size()) << 
-            "Supplied reference keyword/value list contains duplicate keywords";
-
-        // Split the database access string.
-        const LeaseMgr::ParameterMap dbamap = LeaseMgrFactory::parse(dbaccess);
-
-        // It should have the same number keyword value pairs as the
-        EXPECT_EQ(expected_count, dbamap.size());
-
-        // Check that the keywords and keyword values are the same: loop
-        // through the keywords in the database access string.
-        for (LeaseMgr::ParameterMap::const_iterator actual = dbamap.begin();
-             actual != dbamap.end(); ++actual) {
-
-            // Does the keyword exist in the set of expected keywords?
-            map<string, string>::iterator corresponding =
-                expected.find(actual->first);
-            ASSERT_TRUE(corresponding != expected.end());
-
-            // Keyword exists, is the value the same?
-            EXPECT_EQ(corresponding->second, actual->second);
-        }
-    }
-};
-
-// Check that the parser works with a simple configuration.
-TEST_F(DbAccessParserTest, validTypeMemfile) {
-    const char* config[] = {"type", "memfile",
-                            NULL};
-
-    string json_config = toJson(config);
-    ConstElementPtr json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    DbAccessParser parser("lease-database");
-    EXPECT_NO_THROW(parser.build(json_elements));
-    string dbaccess = parser.getDbAccessString();
-    checkAccessString("Valid memfile", dbaccess, config);
-}
-
-// Check that the parser works with a valid MySQL configuration
-TEST_F(DbAccessParserTest, validTypeMysql) {
-    const char* config[] = {"type",     "mysql",
-                            "host",     "erewhon",
-                            "user",     "kea",
-                            "password", "keapassword",
-                            "name",     "keatest",
-                            NULL};
-
-    string json_config = toJson(config);
-    ConstElementPtr json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    DbAccessParser parser("lease-database");
-    EXPECT_NO_THROW(parser.build(json_elements));
-    string dbaccess = parser.getDbAccessString();
-    checkAccessString("Valid mysql", dbaccess, config);
-}
-
-// A missing 'type' keyword should cause an exception to be thrown.
-TEST_F(DbAccessParserTest, missingTypeKeyword) {
-    const char* config[] = {"host",     "erewhon",
-                            "user",     "kea",
-                            "password", "keapassword",
-                            "name",     "keatest",
-                            NULL};
-
-    string json_config = toJson(config);
-    ConstElementPtr json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    DbAccessParser parser("lease-database");
-    EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
-}
-
-// If the value of the "type" keyword is unknown, a BadValue exception should
-// be thrown.
-TEST_F(DbAccessParserTest, badTypeKeyword) {
-    const char* config[] = {"type",     "invalid",
-                            "host",     "erewhon",
-                            "user",     "kea",
-                            "password", "keapassword",
-                            "name",     "keatest",
-                            NULL};
-
-    string json_config = toJson(config);
-    ConstElementPtr json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    DbAccessParser parser("lease-database");
-    EXPECT_THROW(parser.build(json_elements), BadValue);
-}
-
-// Check that the factory function works.
-TEST_F(DbAccessParserTest, factory) {
-    const char* config[] = {"type", "memfile",
-                            NULL};
-
-    string json_config = toJson(config);
-    ConstElementPtr json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    // Check that the parser is built through the factory.
-    boost::scoped_ptr<DhcpConfigParser> parser(
-        DbAccessParser::factory("lease-database"));
-    EXPECT_NO_THROW(parser->build(json_elements));
-
-    // Access the "raw" parser.
-    DbAccessParser* dbap = dynamic_cast<DbAccessParser*>(parser.get());
-    EXPECT_NE(static_cast<DbAccessParser*>(NULL), dbap);
-    string dbaccess = dbap->getDbAccessString();
-    checkAccessString("Valid mysql", dbaccess, config);
-}
-
-// Check reconfiguration.  Checks that incremental changes applied to the
-// database configuration are incremental.
-TEST_F(DbAccessParserTest, incrementalChanges) {
-    const char* config1[] = {"type", "memfile",
-                             NULL};
-
-    // Applying config2 will cause a wholesale change.
-    const char* config2[] = {"type",     "mysql",
-                             "host",     "erewhon",
-                             "user",     "kea",
-                             "password", "keapassword",
-                             "name",     "keatest",
-                             NULL};
-
-    // Applying incremental2 should cause a change to config3.
-    const char* incremental2[] = {"user",     "me",
-                                  "password", "meagain",
-                                  NULL};
-    const char* config3[] = {"type",     "mysql",
-                             "host",     "erewhon",
-                             "user",     "me",
-                             "password", "meagain",
-                             "name",     "keatest",
-                             NULL};
-
-    // incremental3 will cause an exception.  There should be no change
-    // to the returned value.
-    const char* incremental3[] = {"type",     "invalid",
-                                  "user",     "you",
-                                  "password", "youagain",
-                                  NULL};
-
-    // incremental4 is a compatible change and should cause a transition
-    // to config4.
-    const char* incremental4[] = {"user",     "them",
-                                  "password", "themagain",
-                                  NULL};
-    const char* config4[] = {"type",     "mysql",
-                             "host",     "erewhon",
-                             "user",     "them",
-                             "password", "themagain",
-                             "name",     "keatest",
-                             NULL};
-
-    DbAccessParser parser("lease-database");
-
-    // First configuration string should cause a representation of that string
-    // to be held.
-    string json_config = toJson(config1);
-    ConstElementPtr json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    EXPECT_NO_THROW(parser.build(json_elements));
-    string dbaccess = parser.getDbAccessString();
-    checkAccessString("Initial configuration", dbaccess, config1);
-
-    // Applying a wholesale change will cause the access string to change
-    // to a representation of the new configuration.
-    json_config = toJson(config2);
-    json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    EXPECT_NO_THROW(parser.build(json_elements));
-    dbaccess = parser.getDbAccessString();
-    checkAccessString("Subsequent configuration", dbaccess, config2);
-
-    // Applying an incremental change will cause the representation to change
-    // incrementally.
-    json_config = toJson(incremental2);
-    json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    EXPECT_NO_THROW(parser.build(json_elements));
-    dbaccess = parser.getDbAccessString();
-    checkAccessString("Incremental configuration", dbaccess, config3);
-
-    // Applying the next incremental change should cause an exception to be
-    // thrown and there be no change to the access string.
-    json_config = toJson(incremental3);
-    json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    EXPECT_THROW(parser.build(json_elements), BadValue);
-    dbaccess = parser.getDbAccessString();
-    checkAccessString("Incompatible incremental change", dbaccess, config3);
-
-    // Applying an incremental change will cause the representation to change
-    // incrementally.
-    json_config = toJson(incremental4);
-    json_elements = Element::fromJSON(json_config);
-    EXPECT_TRUE(json_elements);
-
-    EXPECT_NO_THROW(parser.build(json_elements));
-    dbaccess = parser.getDbAccessString();
-    checkAccessString("Compatible incremental change", dbaccess, config4);
-}
-
-};  // Anonymous namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 4c02dde..ee0c725 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -59,6 +59,7 @@ public:
     using Dhcpv6Srv::processSolicit;
     using Dhcpv6Srv::processRequest;
     using Dhcpv6Srv::processRenew;
+    using Dhcpv6Srv::processRelease;
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::sanityCheck;
@@ -143,11 +144,14 @@ public:
     }
 
     // Checks that server rejected IA_NA, i.e. that it has no addresses and
-    // that expected status code really appears there.
+    // that expected status code really appears there. In some limited cases
+    // (reply to RELEASE) it may be used to verify positive case, where
+    // IA_NA response is expected to not include address.
+    //
     // Status code indicates type of error encountered (in theory it can also
     // indicate success, but servers typically don't send success status
     // as this is the default result and it saves bandwidth)
-    void checkRejectedIA_NA(const boost::shared_ptr<Option6IA>& ia,
+    void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
                             uint16_t expected_status_code) {
         // Make sure there is no address assigned.
         EXPECT_FALSE(ia->getOption(D6O_IAADDR));
@@ -158,6 +162,12 @@ public:
 
         boost::shared_ptr<OptionCustom> status =
             boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+        // It is ok to not include status success as this is the default behavior
+        if (expected_status_code == STATUS_Success && !status) {
+            return;
+        }
+
         EXPECT_TRUE(status);
 
         if (status) {
@@ -169,6 +179,26 @@ public:
         }
     }
 
+
+    void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+        boost::shared_ptr<OptionCustom> status =
+            boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+        // It is ok to not include status success as this is the default behavior
+        if (expected_status == STATUS_Success && !status) {
+            return;
+        }
+
+        EXPECT_TRUE(status);
+        if (status) {
+            // We don't have dedicated class for status code, so let's just interpret
+            // first 2 bytes as status. Remainder of the status code option content is
+            // just a text explanation what went wrong.
+            EXPECT_EQ(static_cast<uint16_t>(expected_status),
+                      status->readInteger<uint16_t>(0));
+        }
+    }
+
     // Check that generated IAADDR option contains expected address.
     void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
                      const IOAddress& expected_addr,
@@ -340,23 +370,23 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
         "    \"option-data\": [ {"
         "          \"name\": \"OPTION_DNS_SERVERS\","
         "          \"code\": 23,"
-        "          \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
-        "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+        "          \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
+        "          \"csv-format\": True"
         "        },"
         "        {"
         "          \"name\": \"OPTION_FOO\","
         "          \"code\": 1000,"
-        "          \"data\": \"1234\""
+        "          \"data\": \"1234\","
+        "          \"csv-format\": False"
         "        } ]"
         " } ],"
         "\"valid-lifetime\": 4000 }";
 
     ElementPtr json = Element::fromJSON(config);
 
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv(0)));
+    NakedDhcpv6Srv srv(0);
 
-    EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
     ASSERT_TRUE(x);
     comment_ = parseAnswer(rcode_, x);
 
@@ -369,7 +399,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     sol->addOption(clientid);
 
     // Pass it to the server and get an advertise
-    boost::shared_ptr<Pkt6> adv = srv->processSolicit(sol);
+    boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
 
     // check if we get response at all
     ASSERT_TRUE(adv);
@@ -393,7 +423,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     sol->addOption(option_oro);
 
     // Need to process SOLICIT again after requesting new option.
-    adv = srv->processSolicit(sol);
+    adv = srv.processSolicit(sol);
     ASSERT_TRUE(adv);
 
     OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
@@ -444,8 +474,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
 // - server-id
 // - IA that includes IAADDR
 TEST_F(Dhcpv6SrvTest, SolicitBasic) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -454,7 +483,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
     sol->addOption(clientid);
 
     // Pass it to the server and get an advertise
-    Pkt6Ptr reply = srv->processSolicit(sol);
+    Pkt6Ptr reply = srv.processSolicit(sol);
 
     // check if we get response at all
     checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -467,7 +496,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
     checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
 
     // check DUIDs
-    checkServerId(reply, srv->getServerID());
+    checkServerId(reply, srv.getServerID());
     checkClientId(reply, clientid);
 }
 
@@ -487,8 +516,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
 // - server-id
 // - IA that includes IAADDR
 TEST_F(Dhcpv6SrvTest, SolicitHint) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     // Let's create a SOLICIT
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -505,7 +533,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
     sol->addOption(clientid);
 
     // Pass it to the server and get an advertise
-    Pkt6Ptr reply = srv->processSolicit(sol);
+    Pkt6Ptr reply = srv.processSolicit(sol);
 
     // check if we get response at all
     checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -521,7 +549,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
 
     // check DUIDs
-    checkServerId(reply, srv->getServerID());
+    checkServerId(reply, srv.getServerID());
     checkClientId(reply, clientid);
 }
 
@@ -541,8 +569,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
 // - server-id
 // - IA that includes IAADDR
 TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     // Let's create a SOLICIT
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -557,7 +584,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
     sol->addOption(clientid);
 
     // Pass it to the server and get an advertise
-    Pkt6Ptr reply = srv->processSolicit(sol);
+    Pkt6Ptr reply = srv.processSolicit(sol);
 
     // check if we get response at all
     checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -571,7 +598,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
     EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
 
     // check DUIDs
-    checkServerId(reply, srv->getServerID());
+    checkServerId(reply, srv.getServerID());
     checkClientId(reply, clientid);
 }
 
@@ -586,8 +613,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
 // client. ADVERTISE is basically saying "if you send me a request, you will
 // probably get an address like this" (there are no guarantees).
 TEST_F(Dhcpv6SrvTest, ManySolicits) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
@@ -611,9 +637,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
     sol3->addOption(clientid3);
 
     // Pass it to the server and get an advertise
-    Pkt6Ptr reply1 = srv->processSolicit(sol1);
-    Pkt6Ptr reply2 = srv->processSolicit(sol2);
-    Pkt6Ptr reply3 = srv->processSolicit(sol3);
+    Pkt6Ptr reply1 = srv.processSolicit(sol1);
+    Pkt6Ptr reply2 = srv.processSolicit(sol2);
+    Pkt6Ptr reply3 = srv.processSolicit(sol3);
 
     // check if we get response at all
     checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
@@ -634,9 +660,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
     checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
 
     // check DUIDs
-    checkServerId(reply1, srv->getServerID());
-    checkServerId(reply2, srv->getServerID());
-    checkServerId(reply3, srv->getServerID());
+    checkServerId(reply1, srv.getServerID());
+    checkServerId(reply2, srv.getServerID());
+    checkServerId(reply3, srv.getServerID());
     checkClientId(reply1, clientid1);
     checkClientId(reply2, clientid2);
     checkClientId(reply3, clientid3);
@@ -666,8 +692,7 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
 // - server-id
 // - IA that includes IAADDR
 TEST_F(Dhcpv6SrvTest, RequestBasic) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     // Let's create a REQUEST
     Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
@@ -684,10 +709,10 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
     req->addOption(clientid);
 
     // server-id is mandatory in REQUEST
-    req->addOption(srv->getServerID());
+    req->addOption(srv.getServerID());
 
     // Pass it to the server and hope for a REPLY
-    Pkt6Ptr reply = srv->processRequest(req);
+    Pkt6Ptr reply = srv.processRequest(req);
 
     // check if we get response at all
     checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -703,7 +728,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
 
     // check DUIDs
-    checkServerId(reply, srv->getServerID());
+    checkServerId(reply, srv.getServerID());
     checkClientId(reply, clientid);
 
     // check that the lease is really in the database
@@ -720,8 +745,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
 // client. ADVERTISE is basically saying "if you send me a request, you will
 // probably get an address like this" (there are no guarantees).
 TEST_F(Dhcpv6SrvTest, ManyRequests) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
     Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
@@ -745,14 +769,14 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
     req3->addOption(clientid3);
 
     // server-id is mandatory in REQUEST
-    req1->addOption(srv->getServerID());
-    req2->addOption(srv->getServerID());
-    req3->addOption(srv->getServerID());
+    req1->addOption(srv.getServerID());
+    req2->addOption(srv.getServerID());
+    req3->addOption(srv.getServerID());
 
     // Pass it to the server and get an advertise
-    Pkt6Ptr reply1 = srv->processRequest(req1);
-    Pkt6Ptr reply2 = srv->processRequest(req2);
-    Pkt6Ptr reply3 = srv->processRequest(req3);
+    Pkt6Ptr reply1 = srv.processRequest(req1);
+    Pkt6Ptr reply2 = srv.processRequest(req2);
+    Pkt6Ptr reply3 = srv.processRequest(req3);
 
     // check if we get response at all
     checkResponse(reply1, DHCPV6_REPLY, 1234);
@@ -773,9 +797,9 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
     checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
 
     // check DUIDs
-    checkServerId(reply1, srv->getServerID());
-    checkServerId(reply2, srv->getServerID());
-    checkServerId(reply3, srv->getServerID());
+    checkServerId(reply1, srv.getServerID());
+    checkServerId(reply2, srv.getServerID());
+    checkServerId(reply3, srv.getServerID());
     checkClientId(reply1, clientid1);
     checkClientId(reply2, clientid2);
     checkClientId(reply3, clientid3);
@@ -799,8 +823,7 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
 // - returned REPLY message has IA that includes IAADDR
 // - lease is actually renewed in LeaseMgr
 TEST_F(Dhcpv6SrvTest, RenewBasic) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     const IOAddress addr("2001:db8:1:1::cafe:babe");
     const uint32_t iaid = 234;
@@ -841,10 +864,10 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
     req->addOption(clientid);
 
     // Server-id is mandatory in RENEW
-    req->addOption(srv->getServerID());
+    req->addOption(srv.getServerID());
 
     // Pass it to the server and hope for a REPLY
-    Pkt6Ptr reply = srv->processRenew(req);
+    Pkt6Ptr reply = srv.processRenew(req);
 
     // Check if we get response at all
     checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -860,7 +883,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
     checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
 
     // Check DUIDs
-    checkServerId(reply, srv->getServerID());
+    checkServerId(reply, srv.getServerID());
     checkClientId(reply, clientid);
 
     // Check that the lease is really in the database
@@ -895,9 +918,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
 // - returned REPLY message has IA that includes STATUS-CODE
 // - No lease in LeaseMgr
 TEST_F(Dhcpv6SrvTest, RenewReject) {
-
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     const IOAddress addr("2001:db8:1:1::dead");
     const uint32_t transid = 1234;
@@ -925,12 +946,12 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
     req->addOption(clientid);
 
     // Server-id is mandatory in RENEW
-    req->addOption(srv->getServerID());
+    req->addOption(srv.getServerID());
 
     // Case 1: No lease known to server
 
     // Pass it to the server and hope for a REPLY
-    Pkt6Ptr reply = srv->processRenew(req);
+    Pkt6Ptr reply = srv.processRenew(req);
 
     // Check if we get response at all
     checkResponse(reply, DHCPV6_REPLY, transid);
@@ -939,7 +960,7 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
     // Check that IA_NA was returned and that there's an address included
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding);
 
     // Check that there is no lease added
     l = LeaseMgrFactory::instance().getLease6(addr);
@@ -956,14 +977,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
 
     // Pass it to the server and hope for a REPLY
-    reply = srv->processRenew(req);
+    reply = srv.processRenew(req);
     checkResponse(reply, DHCPV6_REPLY, transid);
     tmp = reply->getOption(D6O_IA_NA);
     ASSERT_TRUE(tmp);
     // Check that IA_NA was returned and that there's an address included
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding);
 
     // There is a iaid mis-match, so server should respond that there is
     // no such address to renew.
@@ -975,14 +996,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
     req->addOption(generateClientId(13)); // generate different DUID
                                           // (with length 13)
 
-    reply = srv->processRenew(req);
+    reply = srv.processRenew(req);
     checkResponse(reply, DHCPV6_REPLY, transid);
     tmp = reply->getOption(D6O_IA_NA);
     ASSERT_TRUE(tmp);
     // Check that IA_NA was returned and that there's an address included
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding);
 
     lease = LeaseMgrFactory::instance().getLease6(addr);
     ASSERT_TRUE(lease);
@@ -992,10 +1013,198 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
     EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
 }
 
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseBasic) {
+    NakedDhcpv6Srv srv(0);
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Let's create a RELEASE
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+    OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(released_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RELEASE
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRelease(req);
+
+    // Check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_NA was returned and that there's an address included
+    ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+    checkIA_NAStatusCode(ia, STATUS_Success);
+    checkMsgStatusCode(reply, STATUS_Success);
+
+    // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
+    EXPECT_FALSE(tmp->getOption(D6O_IAADDR));
+
+    // Check DUIDs
+    checkServerId(reply, srv.getServerID());
+    checkClientId(reply, clientid);
+
+    // Check that the lease is really gone in the database
+    // get lease by address
+    l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_FALSE(l);
+
+    // get lease by subnetid/duid/iaid combination
+    l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+    ASSERT_FALSE(l);
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes STATUS-CODE
+// - No lease in LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseReject) {
+
+    NakedDhcpv6Srv srv(0);
+
+    const IOAddress addr("2001:db8:1:1::dead");
+    const uint32_t transid = 1234;
+    const uint32_t valid_iaid = 234;
+    const uint32_t bogus_iaid = 456;
+
+    // Quick sanity check that the address we're about to use is ok
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // GenerateClientId() also sets duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the lease is NOT in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_FALSE(l);
+
+    // Let's create a RELEASE
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, transid));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+    OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(released_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RENEW
+    req->addOption(srv.getServerID());
+
+    // Case 1: No lease known to server
+    SCOPED_TRACE("CASE 1: No lease known to server");
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRelease(req);
+
+    // Check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, transid);
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+    // Check that IA_NA was returned and that there's an address included
+    ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+    ASSERT_TRUE(ia);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkMsgStatusCode(reply, STATUS_NoBinding);
+
+    // Check that the lease is not there
+    l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_FALSE(l);
+
+    // CASE 2: Lease is known and belongs to this client, but to a different IAID
+    SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but to a different IAID");
+
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Pass it to the server and hope for a REPLY
+    reply = srv.processRelease(req);
+    checkResponse(reply, DHCPV6_REPLY, transid);
+    tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+    // Check that IA_NA was returned and that there's an address included
+    ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+    ASSERT_TRUE(ia);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkMsgStatusCode(reply, STATUS_NoBinding);
+
+    // Check that the lease is still there
+    l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // CASE 3: Lease belongs to a client with different client-id
+    SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+    req->delOption(D6O_CLIENTID);
+    ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+    ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+    req->addOption(generateClientId(13)); // generate different DUID
+                                          // (with length 13)
+
+    reply = srv.processRelease(req);
+    checkResponse(reply, DHCPV6_REPLY, transid);
+    tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+    // Check that IA_NA was returned and that there's an address included
+    ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+    ASSERT_TRUE(ia);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkMsgStatusCode(reply, STATUS_NoBinding);
+
+    // Check that the lease is still there
+    l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Finally, let's cleanup the database
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
 // This test verifies if the status code option is generated properly.
 TEST_F(Dhcpv6SrvTest, StatusCode) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     // a dummy content for client-id
     uint8_t expected[] = {
@@ -1005,7 +1214,7 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
         0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
     };
     // Create the option.
-    OptionPtr status = srv->createStatusCode(3, "ABCDE");
+    OptionPtr status = srv.createStatusCode(3, "ABCDE");
     // Allocate an output buffer. We will store the option
     // in wire format here.
     OutputBuffer buf(sizeof(expected));
@@ -1019,34 +1228,34 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
 
 // This test verifies if the sanityCheck() really checks options presence.
 TEST_F(Dhcpv6SrvTest, sanityCheck) {
-    boost::scoped_ptr<NakedDhcpv6Srv> srv;
-    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+    NakedDhcpv6Srv srv(0);
 
     Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
 
-    // check that the packets originating from local addresses can be
+    // Set link-local sender address, so appropriate subnet can be
+    // selected for this packet.
     pkt->setRemoteAddr(IOAddress("fe80::abcd"));
 
     // client-id is optional for information-request, so
-    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
+    EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
 
     // empty packet, no client-id, no server-id
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
                  RFCViolation);
 
     // This doesn't make much sense, but let's check it for completeness
-    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
+    EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
 
     OptionPtr clientid = generateClientId();
     pkt->addOption(clientid);
 
     // client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
-    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+    EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
 
-    pkt->addOption(srv->getServerID());
+    pkt->addOption(srv.getServerID());
 
     // both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
-    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
+    EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
 
     // sane section ends here, let's do some negative tests as well
 
@@ -1054,13 +1263,13 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
     pkt->addOption(clientid);
 
     // with more than one client-id it should throw, no matter what
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
                  RFCViolation);
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
                  RFCViolation);
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
                  RFCViolation);
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
                  RFCViolation);
 
     pkt->delOption(D6O_CLIENTID);
@@ -1069,20 +1278,21 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
     // again we have only one client-id
 
     // let's try different type of insanity - several server-ids
-    pkt->addOption(srv->getServerID());
-    pkt->addOption(srv->getServerID());
+    pkt->addOption(srv.getServerID());
+    pkt->addOption(srv.getServerID());
 
     // with more than one server-id it should throw, no matter what
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
                  RFCViolation);
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
                  RFCViolation);
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
                  RFCViolation);
-    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+    EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
                  RFCViolation);
-
-
 }
 
+/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
+/// to call processX() methods.
+
 }   // end of anonymous namespace
diff --git a/src/bin/loadzone/.gitignore b/src/bin/loadzone/.gitignore
index 286abba..41e280a 100644
--- a/src/bin/loadzone/.gitignore
+++ b/src/bin/loadzone/.gitignore
@@ -1,4 +1,5 @@
 /b10-loadzone
+/b10-loadzone.py
 /loadzone.py
 /run_loadzone.sh
 /b10-loadzone.8
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index d181503..8c55f9b 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -67,7 +67,7 @@
 
     <para>
     Some control entries (aka directives) are supported.
-    $ORIGIN is followed by a domain name, and sets the the origin
+    $ORIGIN is followed by a domain name, and sets the origin
     that will be used for relative domain names in subsequent records.
     $INCLUDE is followed by a filename to load.
     The previous origin is restored after the file is included.
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index 43b7920..b3d61d3 100755
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -18,7 +18,7 @@
 PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
 export PYTHON_EXEC
 
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
@@ -32,5 +32,13 @@ fi
 BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
 export BIND10_MSGQ_SOCKET_FILE
 
+# For bind10_config
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+
+# For data source loadable modules
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
 LOADZONE_PATH=@abs_top_builddir@/src/bin/loadzone
 exec ${LOADZONE_PATH}/b10-loadzone "$@"
diff --git a/src/bin/loadzone/tests/correct/known.test.out b/src/bin/loadzone/tests/correct/known.test.out
index eec692e..377158d 100644
--- a/src/bin/loadzone/tests/correct/known.test.out
+++ b/src/bin/loadzone/tests/correct/known.test.out
@@ -80,6 +80,6 @@ ns5.example.com.		90	IN	A	4.4.4.4
 comment.example.com.		60	IN	SOA	ns1.example.com. hostmaster.example.com. 1 43200 900 1814400 7200
 comment.example.com.		60	IN	NS	ns1.example.com.
 comment.example.com.		60	IN	TXT	"Simple text"
-comment.example.com.		60	IN	TXT	"; No comment"
+comment.example.com.		60	IN	TXT	"\; No comment"
 comment.example.com.		60	IN	TXT	"Also no comment here"
-comment.example.com.		60	IN	TXT	"A combination ; see?"
+comment.example.com.		60	IN	TXT	"A combination \; see?"
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index 4244d07..5f377b1 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -5,10 +5,16 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 pkglibexec_SCRIPTS = b10-msgq
 
 CLEANFILES = b10-msgq msgq.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyc
 
 man_MANS = b10-msgq.8
 DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST = $(man_MANS) msgq.xml
+EXTRA_DIST = $(man_MANS) msgq.xml msgq_messages.mes
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
 
 if GENERATE_DOCS
 
@@ -23,6 +29,11 @@ $(man_MANS):
 
 endif
 
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py : msgq_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message \
+	-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/msgq_messages.mes
+
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
 b10-msgq: msgq.py
 	$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" msgq.py >$@
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index bd13a1c..6937600 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -31,10 +31,16 @@ import select
 import random
 from optparse import OptionParser, OptionValueError
 import isc.util.process
+import isc.log
+from isc.log_messages.msgq_messages import *
 
 import isc.cc
 
 isc.util.process.rename()
+logger = isc.log.Logger("msgq")
+TRACE_START = logger.DBGLVL_START_SHUT
+TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+TRACE_DETAIL = logger.DBGLVL_TRACE_DETAIL
 
 # This is the version that gets displayed to the user.
 # The VERSION string consists of the module name, the module version
@@ -51,11 +57,11 @@ class SubscriptionManager:
         """Add a subscription."""
         target = ( group, instance )
         if target in self.subscriptions:
-            print("[b10-msgq] Appending to existing target")
+            logger.debug(TRACE_BASIC, MSGQ_SUBS_APPEND_TARGET, group, instance)
             if socket not in self.subscriptions[target]:
                 self.subscriptions[target].append(socket)
         else:
-            print("[b10-msgq] Creating new target")
+            logger.debug(TRACE_BASIC, MSGQ_SUBS_NEW_TARGET, group, instance)
             self.subscriptions[target] = [ socket ]
 
     def unsubscribe(self, group, instance, socket):
@@ -162,9 +168,7 @@ class MsgQ:
 
     def setup_listener(self):
         """Set up the listener socket.  Internal function."""
-        if self.verbose:
-            sys.stdout.write("[b10-msgq] Setting up socket at %s\n" %
-                             self.socket_file)
+        logger.debug(TRACE_BASIC, MSGQ_LISTENER_SETUP, self.socket_file)
 
         self.listen_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
 
@@ -179,8 +183,7 @@ class MsgQ:
             if os.path.exists(self.socket_file):
                 os.remove(self.socket_file)
             self.listen_socket.close()
-            sys.stderr.write("[b10-msgq] failed to setup listener on %s: %s\n"
-                             % (self.socket_file, str(e)))
+            logger.fatal(MSGQ_LISTENER_FAILED, self.socket_file, e)
             raise e
 
         if self.poller:
@@ -197,8 +200,7 @@ class MsgQ:
         self.setup_poller()
         self.setup_listener()
 
-        if self.verbose:
-            sys.stdout.write("[b10-msgq] Listening\n")
+        logger.debug(TRACE_START, MSGQ_LISTENER_STARTED);
 
         self.runnable = True
 
@@ -226,7 +228,7 @@ class MsgQ:
     def process_socket(self, fd):
         """Process a read on a socket."""
         if not fd in self.sockets:
-            sys.stderr.write("[b10-msgq] Got read on Strange Socket fd %d\n" % fd)
+            logger.error(MSGQ_READ_UNKNOWN_FD, fd)
             return
         sock = self.sockets[fd]
 #        sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
@@ -243,7 +245,7 @@ class MsgQ:
         del self.sockets[fd]
         if fd in self.sendbuffs:
             del self.sendbuffs[fd]
-        sys.stderr.write("[b10-msgq] Closing socket fd %d\n" % fd)
+        logger.debug(TRACE_BASIC, MSGQ_SOCK_CLOSE, fd)
 
     def getbytes(self, fd, sock, length):
         """Get exactly the requested bytes, or raise an exception if
@@ -285,15 +287,15 @@ class MsgQ:
         try:
             routing, data = self.read_packet(fd, sock)
         except MsgQReceiveError as err:
+            logger.error(MSGQ_RECV_ERR, fd, err)
             self.kill_socket(fd, sock)
-            sys.stderr.write("[b10-msgq] Receive error: %s\n" % err)
             return
 
         try:
             routingmsg = isc.cc.message.from_wire(routing)
         except DecodeError as err:
             self.kill_socket(fd, sock)
-            sys.stderr.write("[b10-msgq] Routing decode error: %s\n" % err)
+            logger.error(MSGQ_HDR_DECODE_ERR, fd, err)
             return
 
         self.process_command(fd, sock, routingmsg, data)
@@ -301,9 +303,7 @@ class MsgQ:
     def process_command(self, fd, sock, routing, data):
         """Process a single command.  This will split out into one of the
            other functions."""
-        # TODO: A print statement got removed here (one that prints the
-        # routing envelope). When we have logging with multiple levels,
-        # we might want to re-add that on a high debug verbosity.
+        logger.debug(TRACE_DETAIL, MSGQ_RECV_HDR, routing)
         cmd = routing["type"]
         if cmd == 'send':
             self.process_command_send(sock, routing, data)
@@ -319,7 +319,7 @@ class MsgQ:
         elif cmd == 'stop':
             self.stop()
         else:
-            sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
+            logger.error(MSGQ_INVALID_CMD, cmd)
 
     def preparemsg(self, env, msg = None):
         if type(env) == dict:
@@ -363,8 +363,8 @@ class MsgQ:
             elif e.errno in [ errno.EPIPE,
                               errno.ECONNRESET,
                               errno.ENOBUFS ]:
-                print("[b10-msgq] " + errno.errorcode[e.errno] +
-                      " on send, dropping message and closing connection")
+                logger.error(MSGQ_SEND_ERR, sock.fileno(),
+                             errno.errorcode[e.errno])
                 self.kill_socket(sock.fileno(), sock)
                 return None
             else:
@@ -491,7 +491,7 @@ class MsgQ:
                 if err.args[0] == errno.EINTR:
                     events = []
                 else:
-                    sys.stderr.write("[b10-msgq] Error with poll(): %s\n" % err)
+                    logger.fatal(MSGQ_POLL_ERR, err)
                     break
             for (fd, event) in events:
                 if fd == self.listen_socket.fileno():
@@ -502,7 +502,7 @@ class MsgQ:
                     elif event & select.POLLIN:
                         self.process_socket(fd)
                     else:
-                        print("[b10-msgq] Error: Unknown even in run_poller()")
+                        logger.error(MSGQ_POLL_UNKNOWN_EVENT, fd, event)
 
     def run_kqueue(self):
         while self.running:
@@ -563,18 +563,25 @@ if __name__ == "__main__":
                       help="UNIX domain socket file the msgq daemon will use")
     (options, args) = parser.parse_args()
 
+    # Init logging, according to the parameters.
+    # FIXME: Do proper logger configuration, this is just a hack
+    # This is #2582
+    sev = 'INFO'
+    if options.verbose:
+        sev = 'DEBUG'
+    isc.log.init("b10-msgq", buffer=False, severity=sev, debuglevel=99)
+
     signal.signal(signal.SIGTERM, signal_handler)
 
     # Announce startup.
-    if options.verbose:
-        sys.stdout.write("[b10-msgq] %s\n" % VERSION)
+    logger.debug(TRACE_START, MSGQ_START, VERSION)
 
     msgq = MsgQ(options.msgq_socket_file, options.verbose)
 
     try:
         msgq.setup()
     except Exception as e:
-        sys.stderr.write("[b10-msgq] Error on startup: %s\n" % str(e))
+        logger.fatal(MSGQ_START_FAIL, e)
         sys.exit(1)
 
     try:
diff --git a/src/bin/msgq/msgq.xml b/src/bin/msgq/msgq.xml
index fed4c27..43c72a4 100644
--- a/src/bin/msgq/msgq.xml
+++ b/src/bin/msgq/msgq.xml
@@ -111,7 +111,7 @@
         <listitem><para>
           The UNIX domain socket file this daemon will use.
           The default is
-          <filename>/usr/local/var/bind10-devel/msg_socket</filename>.
+          <filename>/usr/local/var/bind10/msg_socket</filename>.
 <!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
           </para></listitem>
       </varlistentry>
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
new file mode 100644
index 0000000..21c5aa8
--- /dev/null
+++ b/src/bin/msgq/msgq_messages.mes
@@ -0,0 +1,88 @@
+# Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the ddns messages python module.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% MSGQ_HDR_DECODE_ERR Error decoding header received from socket %1: %2
+The socket with mentioned file descriptor sent a packet. However, it was not
+possible to decode the routing header of the packet. The packet is ignored.
+This may be caused by a programmer error (one of the components sending invalid
+data) or possibly by incompatible version of msgq and the component (but that's
+unlikely, as the protocol is not changed often).
+
+% MSGQ_LISTENER_FAILED Failed to initialize listener on socket file '%1': %2
+The message queue daemon tried to listen on a file socket (the path is in the
+message), but it failed. The error from the operating system is logged.
+
+% MSGQ_LISTENER_SETUP Starting to listen on socket file '%1'
+Debug message. The listener is trying to open a listening socket.
+
+% MSGQ_LISTENER_STARTED Successfully started to listen
+Debug message. The message queue successfully opened a listening socket and
+waits for incoming connections.
+
+% MSGQ_POLL_ERR Error while polling for events: %1
+A low-level error happened when waiting for events, the error is logged. The
+reason for this varies, but it usually means the system is short on some
+resources.
+
+% MSGQ_POLL_UNKNOWN_EVENT Got an unknown event from the poller for fd %1: %2
+An unknown event got out from the poll() system call. This should generally not
+happen and it is either a programmer error or OS bug. The event is ignored. The
+number noted as the event is the raw encoded value, which might be useful to
+the authors when figuring the problem out.
+
+% MSGQ_READ_UNKNOWN_FD Got read on strange socket %1
+The OS reported a file descriptor is ready to read. But the daemon doesn't know
+the mentioned file descriptor, which is either a programmer error or OS bug.
+The read event is ignored.
+
+% MSGQ_RECV_ERR Error reading from socket %1: %2
+There was a low-level error when reading from a socket. The error is logged and
+the corresponding socket is dropped.
+
+% MSGQ_RECV_HDR Received header: %1
+Debug message. This message includes the whole routing header of a packet.
+
+% MSGQ_INVALID_CMD Received invalid command: %1
+An unknown command listed in the log has been received. It is ignored. This
+indicates either a programmer error (eg. a typo in the command name) or
+incompatible version of a module and message queue daemon.
+
+% MSGQ_SEND_ERR Error while sending to socket %1: %2
+There was a low-level error when sending data to a socket. The error is logged
+and the corresponding socket is dropped.
+
+% MSGQ_SOCK_CLOSE Closing socket fd %1
+Debug message. Closing the mentioned socket.
+
+% MSGQ_START Msgq version %1 starting
+Debug message. The message queue is starting up.
+
+% MSGQ_START_FAIL Error during startup: %1
+There was an error during early startup of the daemon. More concrete error is
+in the log. The daemon terminates as a result.
+
+% MSGQ_SUBS_APPEND_TARGET Appending to existing target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription by appending it to already existing
+data structure.
+
+% MSGQ_SUBS_NEW_TARGET Creating new target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription. Also creating a new data structure
+to hold it.
diff --git a/src/bin/msgq/run_msgq.sh.in b/src/bin/msgq/run_msgq.sh.in
index 3e464be..3ab4024 100644
--- a/src/bin/msgq/run_msgq.sh.in
+++ b/src/bin/msgq/run_msgq.sh.in
@@ -20,9 +20,25 @@ export PYTHON_EXEC
 
 MYPATH_PATH=@abs_top_builddir@/src/bin/msgq
 
-PYTHONPATH=@abs_top_srcdir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/log/.libs
 export PYTHONPATH
 
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+    @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+    export @ENV_LIBRARY_PATH@
+fi
+
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+# TODO: We need to do this feature based (ie. no general from_source)
+# But right now we need a second one because some spec files are
+# generated and hence end up under builddir
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
 BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
 export BIND10_MSGQ_SOCKET_FILE
 
diff --git a/src/bin/msgq/tests/Makefile.am b/src/bin/msgq/tests/Makefile.am
index 50b218b..c9ef5d3 100644
--- a/src/bin/msgq/tests/Makefile.am
+++ b/src/bin/msgq/tests/Makefile.am
@@ -21,6 +21,8 @@ endif
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/msgq \
 	BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
+	B10_FROM_SOURCE=$(abs_top_srcdir) \
+	B10_FROM_BUILD=$(abs_top_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 
diff --git a/src/bin/msgq/tests/msgq_test.in b/src/bin/msgq/tests/msgq_test.in
deleted file mode 100755
index e3aae89..0000000
--- a/src/bin/msgq/tests/msgq_test.in
+++ /dev/null
@@ -1,28 +0,0 @@
-#! /bin/sh
-
-# Copyright (C) 2010  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.
-
-PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
-export PYTHON_EXEC
-
-MYPATH_PATH=@abs_top_srcdir@/src/bin/msgq/tests
-
-PYTHONPATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/lib/python
-
-export PYTHONPATH
-
-cd ${MYPATH_PATH}
-exec ${PYTHON_EXEC} -O msgq_test.py $*
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 4060190..417418f 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -10,6 +10,7 @@ import errno
 import threading
 import isc.cc
 import collections
+import isc.log
 
 #
 # Currently only the subscription part and some sending is implemented...
@@ -457,4 +458,6 @@ class SendNonblock(unittest.TestCase):
 
 
 if __name__ == '__main__':
+    isc.log.init("b10-msgq")
+    isc.log.resetUnitTestRootLogger()
     unittest.main()
diff --git a/src/bin/resolver/b10-resolver.xml b/src/bin/resolver/b10-resolver.xml
index aca8fb2..485d022 100644
--- a/src/bin/resolver/b10-resolver.xml
+++ b/src/bin/resolver/b10-resolver.xml
@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>February 28, 2012</date>
+    <date>August 16, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -148,6 +148,8 @@ once that is merged you can for instance do 'config add Resolver/forward_address
       address or special keyword.
       The <varname>key</varname> is a TSIG key name.
       The default configuration accepts queries from 127.0.0.1 and ::1.
+      The default action is REJECT for newly added
+      <varname>query_acl</varname> items.
     </para>
 
     <para>
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index cc0f09f..725aa85 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -200,14 +200,14 @@ public:
     /**
      * \short Get info about timeouts.
      *
-     * \returns Timeout and retries (as described in setTimeouts).
+     * \return Timeout and retries (as described in setTimeouts).
      */
     std::pair<int, unsigned> getTimeouts() const;
 
     /**
      * \brief Get the timeout for outgoing queries
      *
-     * \returns Timeout for outgoing queries
+     * \return Timeout for outgoing queries
      */
     int getQueryTimeout() const;
 
@@ -218,7 +218,7 @@ public:
      * (internal resolving on the query will continue, see
      * \c getLookupTimeout())
      * 
-     * \returns Timeout for outgoing queries
+     * \return Timeout for outgoing queries
      */
     int getClientTimeout() const;
 
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
index 17b7671..28d6ac9 100644
--- a/src/bin/stats/b10-stats-httpd.xml
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -103,7 +103,7 @@
   <refsect1>
     <title>FILES</title>
     <para>
-      <filename>/usr/local/share/bind10-devel/stats-httpd.spec</filename>
+      <filename>/usr/local/share/bind10/stats-httpd.spec</filename>
       <!--TODO: The filename should be computed from prefix-->
       — the spec file of <command>b10-stats-httpd</command>. This file
       contains configurable settings
@@ -115,17 +115,17 @@
       how to configure the settings.
     </para>
     <para>
-      <filename>/usr/local/share/bind10-devel/stats-httpd-xml.tpl</filename>
+      <filename>/usr/local/share/bind10/stats-httpd-xml.tpl</filename>
       <!--TODO: The filename should be computed from prefix-->
       — the template file of XML document.
     </para>
     <para>
-      <filename>/usr/local/share/bind10-devel/stats-httpd-xsd.tpl</filename>
+      <filename>/usr/local/share/bind10/stats-httpd-xsd.tpl</filename>
       <!--TODO: The filename should be computed from prefix-->
       — the template file of XSD document.
     </para>
     <para>
-      <filename>/usr/local/share/bind10-devel/stats-httpd-xsl.tpl</filename>
+      <filename>/usr/local/share/bind10/stats-httpd-xsl.tpl</filename>
       <!--TODO: The filename should be computed from prefix-->
       — the template file of XSL document.
     </para>
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 88a01ed..ee89ad2 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -210,7 +210,7 @@
 
   <refsect1>
     <title>FILES</title>
-    <para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
+    <para><filename>/usr/local/share/bind10/stats.spec</filename>
       <!--TODO: The filename should be computed from prefix-->
       — This is a spec file for <command>b10-stats</command>. It
       contains commands for <command>b10-stats</command>. They can be
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 837cafa..770a8b2 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -24,7 +24,7 @@ The serial fields of the first and last SOAs of AXFR (including AXFR-style
 IXFR) are not the same.  According to RFC 5936 these two SOAs must be the
 "same" (not only for the serial), but it is still not clear what the
 receiver should do if this condition does not hold.  There was a discussion
-about this at the IETF dnsext wg:
+about this at the IETF dnsext working group:
 http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
 and the general feeling seems that it would be better to reject the
 transfer if a mismatch is detected.  On the other hand, also as noted
@@ -61,10 +61,10 @@ There was an error opening a connection to the master. The error is
 shown in the log message.
 
 % XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
-In an attempt of IXFR processing, the begenning SOA of the first difference
+In an attempt of IXFR processing, the beginning SOA of the first difference
 (following the initial SOA that specified the final SOA for all the
 differences) was found.  This means a connection for xfrin tried IXFR
-and really aot a response for incremental updates.
+and really got a response for incremental updates.
 
 % XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
 Non incremental transfer was detected at the "first data" of a transfer,
@@ -149,16 +149,16 @@ daemon will now shut down.
 The AXFR transfer of the given zone was successful.
 The provided information contains the following values:
 
-messages: Number of overhead DNS messages in the transfer
+messages: Number of overhead DNS messages in the transfer.
 
 records: Number of Resource Records in the full transfer, excluding the
 final SOA record that marks the end of the AXFR.
 
 bytes: Full size of the transfer data on the wire.
 
-run time: Time (in seconds) the complete axfr took
+run time: Time (in seconds) the complete axfr took.
 
-bytes/second: Transfer speed
+bytes/second: Transfer speed.
 
 % XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1
 An attempt to start a transfer with TSIG was made, but the configured TSIG
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index 97b7a31..d48aa24 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -70,7 +70,7 @@ AXFR-style IXFR.
 
 % XFROUT_IXFR_NO_ZONE IXFR client %1, %2: zone not found with journal
 The requested zone in IXFR was not found in the data source
-even though the xfrout daemon sucessfully found the SOA RR of the zone
+even though the xfrout daemon successfully found the SOA RR of the zone
 in the data source.  This can happen if the administrator removed the
 zone from the data source within the small duration between these
 operations, but it's more likely to be a bug or broken data source.
@@ -84,9 +84,6 @@ NOTAUTH.
 An IXFR request was received, but the client's SOA version is the same as
 or newer than that of the server.  The xfrout server responds to the
 request with the answer section being just one SOA of that version.
-Note: as of this wrting the 'newer version' cannot be identified due to
-the lack of support for the serial number arithmetic.  This will soon
-be implemented.
 
 % XFROUT_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
 There was a problem in the lower level module handling configuration and
@@ -206,7 +203,7 @@ xfrout daemon process is still running. This xfrout daemon (the one
 printing this message) will not start.
 
 % XFROUT_XFR_TRANSFER_CHECK_ERROR %1 client %2: check for transfer of %3 failed: %4
-Pre-response check for an incomding XFR request failed unexpectedly.
+Pre-response check for an incoming XFR request failed unexpectedly.
 The most likely cause of this is that some low level error in the data
 source, but it may also be other general (more unlikely) errors such
 as memory shortage.  Some detail of the error is also included in the
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index 8d9b87d..4a37087 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -308,7 +308,7 @@ std::string
 str_from_stringstream(std::istream &in, const std::string& file, const int line,
                       int& pos) throw (JSONError)
 {
-    char c = 0;
+    char c;
     std::stringstream ss;
     c = in.get();
     ++pos;
@@ -390,7 +390,7 @@ number_from_stringstream(std::istream &in, int& pos) {
 // value is larger than an int can handle)
 ElementPtr
 from_stringstream_number(std::istream &in, int &pos) {
-    long int i = 0;
+    long int i;
     double d = 0.0;
     bool is_double = false;
     char *endptr;
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index bb84ae2..db25d9f 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -109,8 +109,7 @@ public:
 
     /// \name pure virtuals, every derived class must implement these
 
-    /// \returns true if the other ElementPtr has the same type and
-    ///          value
+    /// \return true if the other ElementPtr has the same type and value
     virtual bool equals(const Element& other) const = 0;
 
     /// Converts the Element to JSON format and appends it to
diff --git a/src/lib/config/config_data.cc b/src/lib/config/config_data.cc
index ebe51cc..fb5dd75 100644
--- a/src/lib/config/config_data.cc
+++ b/src/lib/config/config_data.cc
@@ -235,7 +235,7 @@ ConfigData::getItemList(const std::string& identifier, bool recurse) const {
 ConstElementPtr
 ConfigData::getFullConfig() const {
     ElementPtr result = Element::createMap();
-    ConstElementPtr items = getItemList("", true);
+    ConstElementPtr items = getItemList("", false);
     BOOST_FOREACH(ConstElementPtr item, items->listValue()) {
         result->set(item->stringValue(), getValue(item->stringValue()));
     }
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 0bb1bfd..e40600d 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -93,8 +93,8 @@ public:
     void setLocalConfig(isc::data::ElementPtr config) { _config = config; }
 
     /// Returns the local (i.e. non-default) configuration.
-    /// \returns An ElementPtr pointing to a MapElement containing all
-    ///          non-default configuration options.
+    /// \return An ElementPtr pointing to a MapElement containing all
+    ///         non-default configuration options.
     isc::data::ElementPtr getLocalConfig() { return (_config); }
 
     /// Returns a list of all possible configuration options as specified
@@ -110,11 +110,11 @@ public:
     isc::data::ConstElementPtr getItemList(const std::string& identifier = "",
                                            bool recurse = false) const;
 
-    /// Returns all current configuration settings (both non-default and default).
+    /// Returns a map of the top-level configuration items, as currently
+    /// set or their defaults
+    ///
     /// \return An ElementPtr pointing to a MapElement containing
-    ///         string->value elements, where the string is the
-    ///         full identifier of the configuration option and the
-    ///         value is an ElementPtr with the value.
+    ///         the top-level configuration items
     isc::data::ConstElementPtr getFullConfig() const;
 
 private:
@@ -126,6 +126,6 @@ private:
 }
 #endif
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:
diff --git a/src/lib/config/tests/config_data_unittests.cc b/src/lib/config/tests/config_data_unittests.cc
index 26a3fc6..4b83e5c 100644
--- a/src/lib/config/tests/config_data_unittests.cc
+++ b/src/lib/config/tests/config_data_unittests.cc
@@ -118,7 +118,7 @@ TEST(ConfigData, getLocalConfig) {
     ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
     ConfigData cd = ConfigData(spec2);
     EXPECT_EQ("{  }", cd.getLocalConfig()->str());
-    
+
     ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
     cd.setLocalConfig(my_config);
     EXPECT_EQ("{ \"item1\": 2 }", cd.getLocalConfig()->str());
@@ -141,12 +141,15 @@ TEST(ConfigData, getFullConfig) {
     ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
     ConfigData cd = ConfigData(spec2);
 
-    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": {  } }", cd.getFullConfig()->str());
     ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
     cd.setLocalConfig(my_config);
-    EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+    EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": {  } }", cd.getFullConfig()->str());
     ElementPtr my_config2 = Element::fromJSON("{ \"item6\": { \"value1\": \"a\" } }");
     cd.setLocalConfig(my_config2);
-    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
+    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value1\": \"a\" } }", cd.getFullConfig()->str());
+    ElementPtr my_config3 = Element::fromJSON("{ \"item6\": { \"value2\": 123 } }");
+    cd.setLocalConfig(my_config3);
+    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value2\": 123 } }", cd.getFullConfig()->str());
 }
 
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index d791fbc..f377ffe 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -1745,7 +1745,7 @@ public:
                 arg(zone_).arg(rrclass_).arg(accessor_->getDBName());
             return (rrset);
         } catch (const Exception& ex) {
-            LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADR_BADDATA).
+            LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADER_BADDATA).
                 arg(zone_).arg(rrclass_).arg(accessor_->getDBName()).
                 arg(begin_).arg(end_).arg(ex.what());
             isc_throw(DataSourceError, "Failed to create RRset from diff on "
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index e9b4c90..6983459 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -144,7 +144,7 @@ instead.
 % DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1
 The domain name does not have any RRs associated with it, so it doesn't
 exist in the database.  However, it has a subdomain, so it does exist
-in the DNS address space. This type of domain is known an an "empty
+in the DNS address space. This type of domain is known as an "empty
 non-terminal" and so we return NXRRSET instead of NXDOMAIN.
 
 % DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
@@ -202,7 +202,7 @@ a zone's difference sequences from a database-based data source.  The
 zone's name and class, database name, and the start and end serials
 are shown in the message.
 
-% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
+% DATASRC_DATABASE_JOURNALREADER_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
 This is an error message indicating that a zone's diff is broken and
 the data source library failed to convert it to a valid RRset.  The
 most likely cause of this is that someone has manually modified the
@@ -287,7 +287,7 @@ matching the name.  This is returned as the result of the search.
 The given wildcard matches the name being sough but it as an empty
 nonterminal (e.g. there's nothing at *.example.com but something like
 subdomain.*.example.org, do exist: so *.example.org exists in the
-namespace but has no RRs assopciated with it). This will produce NXRRSET.
+namespace but has no RRs associated with it). This will produce NXRRSET.
 
 % DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
 The database doesn't contain directly matching name.  When searching
@@ -490,7 +490,7 @@ destroyed.
 % DATASRC_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
+behaviour is specified by RFC 1034, section 4.3.3.
 
 % DATASRC_MEM_WILDCARD_DNAME DNAME record in wildcard domain '%1'
 The software refuses to load DNAME records into a wildcard domain.  It isn't
diff --git a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
index 58c6cb1..4f82f7b 100644
--- a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
@@ -409,6 +409,7 @@ public:
         reader.nextSig();
         reader.rewind();
         // Do the actual rendering
+        // cppcheck-suppress unreadVariable
         current = &renderer;
         reader.iterate();
         renderer.writeName(dummyName2());
@@ -484,6 +485,8 @@ public:
         current = NULL;
         reader.iterateAllSigs();
         // Now return the renderer and render the rest of the data
+        // cppcheck-suppress redundantAssignment
+        // cppcheck-suppress unreadVariable
         current = &renderer;
         reader.iterate();
         // Now, this should not break anything and should be valid, but should
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 6844712..85b167e 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -81,7 +81,7 @@ addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
 }
 
 DataSourceClientPtr
-createSQLite3Client(RRClass zclass, const Name& zname) {
+createSQLite3Client(RRClass zclass, const Name& zname, stringstream& ss) {
     // We always begin with an empty template SQLite3 DB file and install
     // the zone data from the zone file to ensure both cases have the
     // same test data.
@@ -93,7 +93,6 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
     // Note that neither updater nor SQLite3 accessor checks this condition,
     // so this should succeed.
     ZoneUpdaterPtr updater = client->getUpdater(zname, false);
-    stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
     masterLoad(ss, Name::ROOT_NAME(), zclass,
                boost::bind(addRRset, updater, _1));
     updater->commit();
@@ -101,6 +100,12 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
     return (client);
 }
 
+DataSourceClientPtr
+createSQLite3ClientWithNS(RRClass zclass, const Name& zname) {
+    stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
+    return (createSQLite3Client(zclass, zname, ss));
+}
+
 // The test class.  Its parameterized so we can share the test scnearios
 // for any concrete data source implementaitons.
 class ZoneFinderContextTest :
@@ -134,7 +139,7 @@ protected:
 // We test the in-memory and SQLite3 data source implementations.
 INSTANTIATE_TEST_CASE_P(, ZoneFinderContextTest,
                         ::testing::Values(createInMemoryClient,
-                                          createSQLite3Client));
+                                          createSQLite3ClientWithNS));
 
 TEST_P(ZoneFinderContextTest, getAdditionalAuthNS) {
     ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::NS());
@@ -430,4 +435,25 @@ TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
                 result_sets_.begin(), result_sets_.end());
 }
 
+TEST(ZoneFinderContextSQLite3Test, escapedText) {
+    // This test checks that TXTLike data, when written to a database,
+    // is escaped correctly before stored in the database. The actual
+    // escaping is done in the toText() method of TXTLike objects, but
+    // we check anyway if this also carries over to the ZoneUpdater.
+    RRClass zclass(RRClass::IN());
+    Name zname("example.org");
+    stringstream ss("escaped.example.org. 3600 IN TXT Hello~World\\;\\\"");
+    DataSourceClientPtr client = createSQLite3Client(zclass, zname, ss);
+    ZoneFinderPtr finder = client->findZone(zname).zone_finder;
+
+    // If there is no escaping, the following will throw an exception
+    // when it tries to construct a TXT RRset using the data from the
+    // database.
+    ZoneFinderContextPtr ctx = finder->find(Name("escaped.example.org"),
+                                            RRType::TXT());
+    EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+    EXPECT_EQ("escaped.example.org. 3600 IN TXT \"Hello~World\\;\\\"\"\n",
+              ctx->rrset->toText());
+}
+
 }
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 571f11d..7305e0c 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -14,23 +14,23 @@ CLEANFILES = *.gcno *.gcda
 
 lib_LTLIBRARIES = libb10-dhcp++.la
 libb10_dhcp___la_SOURCES  =
+libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libb10_dhcp___la_SOURCES += duid.cc duid.h
 libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
-libb10_dhcp___la_SOURCES += option.cc option.h
-libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
-libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
-libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
+libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
 libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
 libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
-libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
 libb10_dhcp___la_SOURCES += option_int.h
 libb10_dhcp___la_SOURCES += option_int_array.h
-libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
+libb10_dhcp___la_SOURCES += option.cc option.h
+libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
+libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
+libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 libb10_dhcp___la_SOURCES += std_option_defs.h
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index 91efe94..f1c8866 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -95,6 +95,16 @@ const std::vector<uint8_t> ClientId::getClientId() const {
     return (duid_);
 }
 
+// Returns the Client ID in text form
+std::string ClientId::toText() const {
+
+    // As DUID is a private base class of ClientId, we can't access
+    // its public toText() method through inheritance: instead we
+    // need the interface of a ClientId::toText() that calls the
+    // equivalent method in the base class.
+    return (DUID::toText());
+}
+
 // Compares two client-ids
 bool ClientId::operator==(const ClientId& other) const {
     return (this->duid_ == other.duid_);
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 60b9706..a4e32b3 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -107,6 +107,9 @@ public:
     /// @brief Returns reference to the client-id data
     const std::vector<uint8_t> getClientId() const;
 
+    /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
+    std::string toText() const;
+
     /// @brief Compares two client-ids for equality
     bool operator==(const ClientId& other) const;
 
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index b19ed21..a3921cc 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -74,6 +74,34 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
     return (OptionDefinitionPtr());
 }
 
+bool
+LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
+    if (u == Option::V6) {
+        if (code < 79 &&
+            code != 10 &&
+            code != 35) {
+            return (true);
+        }
+
+    } else if (u == Option::V4) {
+        if (!(code == 84 ||
+              code == 96 ||
+              (code > 101 && code < 112) ||
+              code == 115 ||
+              code == 126 ||
+              code == 127 ||
+              (code > 146 && code < 150) ||
+              (code > 177  && code < 208) ||
+              (code > 213 && code <  220) ||
+              (code > 221 && code < 224))) {
+                return (true);
+            }
+
+    }
+
+    return (false);
+}
+
 OptionPtr
 LibDHCP::optionFactory(Option::Universe u,
                        uint16_t type,
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c325aa5..bc47405 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -55,6 +55,21 @@ public:
     static OptionDefinitionPtr getOptionDef(const Option::Universe u,
                                             const uint16_t code);
 
+    /// @brief Check if the specified option is a standard option.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param code option code.
+    ///
+    /// @return true if the specified option is a standard option.
+    /// @todo We arleady create option definitions for the subset if
+    /// standard options. We are aiming that this function checks
+    /// the presence of the standard option definition and if it finds
+    /// it, then the true value is returned. However, at this point
+    /// this is not doable because some of the definitions (for less
+    /// important options) are not created yet.
+    static bool isStandardOption(const Option::Universe u,
+                                 const uint16_t code);
+
     /// @brief Factory function to create instance of option.
     ///
     /// Factory method creates instance of specified option. The option
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index e1d7cb6..d97ca71 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -22,6 +22,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <util/encode/hex.h>
+#include <util/strutil.h>
 
 using namespace std;
 using namespace isc::util;
@@ -176,10 +177,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
         if (values.empty()) {
             isc_throw(InvalidOptionValue, "no option value specified");
         }
-        writeToBuffer(values[0], type_, buf);
+        writeToBuffer(util::str::trim(values[0]), type_, buf);
     } else if (array_type_ && type_ != OPT_RECORD_TYPE) {
         for (size_t i = 0; i < values.size(); ++i) {
-            writeToBuffer(values[i], type_, buf);
+            writeToBuffer(util::str::trim(values[i]), type_, buf);
         }
     } else if (type_ == OPT_RECORD_TYPE) {
         const RecordFieldsCollection& records = getRecordFields();
@@ -189,7 +190,8 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                       << " provided.");
         }
         for (size_t i = 0; i < records.size(); ++i) {
-            writeToBuffer(values[i], records[i], buf);
+            writeToBuffer(util::str::trim(values[i]),
+                          records[i], buf);
         }
     }
     return (optionFactory(u, type, buf.begin(), buf.end()));
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 9f6bef2..efcaba0 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -42,6 +42,14 @@ public:
         isc::Exception(file, line, what) { };
 };
 
+/// @brief Exception to be thrown when the particular option definition
+/// duplicates existing option definition.
+class DuplicateOptionDefinition : public Exception {
+public:
+    DuplicateOptionDefinition(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 /// @brief Forward declaration to OptionDefinition.
 class OptionDefinition;
 
@@ -361,7 +369,7 @@ public:
 
     /// @brief Factory function to create option with array of integer values.
     ///
-    /// @param universe (V4 or V6).
+    /// @param u universe (V4 or V6).
     /// @param type option type.
     /// @param begin iterator pointing to the beginning of the buffer.
     /// @param end iterator pointing to the end of the buffer.
@@ -492,6 +500,9 @@ typedef boost::multi_index_container<
     >
 > OptionDefContainer;
 
+/// Pointer to an option definition container.
+typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+
 /// Type of the index #1 - option type.
 typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
 /// Pair of iterators to represent the range of options definitions
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 9db4c35..de20e51 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -108,6 +108,14 @@ TEST(DuidTest, getType) {
     EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
 }
 
+// Test checks if the toText() returns valid texual representation
+TEST(DuidTest, toText) {
+    uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+    DUID duid(data1, sizeof(data1));
+    EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+}
+
 // This test checks if the comparison operators are sane.
 TEST(DuidTest, operators) {
     uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
@@ -174,8 +182,8 @@ TEST(ClientIdTest, operators) {
 TEST(ClientIdTest, toText) {
     uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
 
-    DUID duid(data1, sizeof(data1));
-    EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+    ClientId clientid(data1, sizeof(data1));
+    EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
 }
 
 } // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index e44ef58..a59da12 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -453,6 +453,66 @@ TEST_F(LibDhcpTest, unpackOptions4) {
     EXPECT_TRUE(x == options.end()); // option 2 not found
 }
 
+TEST_F(LibDhcpTest, isStandardOption4) {
+    // Get all option codes that are not occupied by standard options.
+    const uint16_t unassigned_codes[] = { 84, 96, 102, 103, 104, 105, 106, 107, 108,
+                                          109, 110, 111, 115, 126, 127, 147, 148, 149,
+                                          178, 179, 180, 181, 182, 183, 184, 185, 186,
+                                          187, 188, 189, 190, 191, 192, 193, 194, 195,
+                                          196, 197, 198, 199, 200, 201, 202, 203, 204,
+                                          205, 206, 207, 214, 215, 216, 217, 218, 219,
+                                          222, 223 };
+    const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
+
+    // Try all possible option codes.
+    for (size_t i = 0; i < 256; ++i) {
+        // Some ranges of option codes are unassigned and thus the isStandardOption
+        // should return false for them.
+        bool check_unassigned = false;
+        // Check the array of unassigned options to find out whether option code
+        // is assigned to standard option or unassigned.
+        for (size_t j = 0; j < unassigned_num; ++j) {
+            // If option code is found within the array of unassigned options
+            // we the isStandardOption function should return false.
+            if (unassigned_codes[j] == i) {
+                check_unassigned = true;
+                EXPECT_FALSE(LibDHCP::isStandardOption(Option::V4,
+                                                       unassigned_codes[j]))
+                    << "Test failed for option code " << unassigned_codes[j];
+                break;
+            }
+        }
+        // If the option code belongs to the standard option then the
+        // isStandardOption should return true.
+        if (!check_unassigned) {
+            EXPECT_TRUE(LibDHCP::isStandardOption(Option::V4, i))
+                << "Test failed for the option code " << i;
+        }
+    }
+}
+
+TEST_F(LibDhcpTest, isStandardOption6) {
+    // All option codes in the range from 0 to 78 (except 10 and 35)
+    // identify the standard options.
+    for (uint16_t code = 0; code < 79; ++code) {
+        if (code != 10 && code != 35) {
+            EXPECT_TRUE(LibDHCP::isStandardOption(Option::V6, code))
+                << "Test failed for option code " << code;
+        }
+    }
+
+    // Check the option codes 10 and 35. They are unassigned.
+    EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 10));
+    EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 35));
+
+    // Check a range of option codes above 78. Those are option codes
+    // identifying non-standard options.
+    for (uint16_t code = 79; code < 512; ++code) {
+        EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, code))
+            << "Test failed for option code " << code;
+    }
+}
+
 TEST_F(LibDhcpTest, stdOptionDefs4) {
 
     // Create a buffer that holds dummy option data.
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index b44fa7d..f62fd52 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -8,29 +8,49 @@ endif
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
+# Define rule to build logging source files from message file
+dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+# Tell Automake that the dhcpsrv_messages.{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 = dhcpsrv_messages.h dhcpsrv_messages.cc
+
 # 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
+# Make sure the generated files are deleted in a "clean" operation
+CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc
 
 lib_LTLIBRARIES = libb10-dhcpsrv.la
 libb10_dhcpsrv_la_SOURCES  =
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
+libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
+libb10_dhcpsrv_la_SOURCES += hwaddr.cc hwaddr.h
 libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
 if HAVE_MYSQL
 libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 endif
+libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
 libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
 libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libb10_dhcpsrv_la_SOURCES += triplet.h
 
+nodist_libb10_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+
 libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcpsrv_la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
@@ -47,6 +67,9 @@ if USE_CLANGPP
 libb10_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
 endif
 
+# The message file should be in the distribution
+EXTRA_DIST = dhcpsrv_messages.mes
+
 # Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST = dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
+EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
 dist_pkgdata_DATA = dhcpdb_create.mysql
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 7dc5f55..26ee978 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -13,7 +13,9 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <asiolink/io_address.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
 
 using namespace isc::asiolink;
 using namespace isc::util;
@@ -21,15 +23,120 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
-
-
-
 CfgMgr&
 CfgMgr::instance() {
     static CfgMgr cfg_mgr;
     return (cfg_mgr);
 }
 
+void
+CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
+    if (!space) {
+        isc_throw(InvalidOptionSpace,
+                  "provided option space object is NULL.");
+    }
+    OptionSpaceCollection::iterator it = spaces4_.find(space->getName());
+    if (it != spaces4_.end()) {
+        isc_throw(InvalidOptionSpace, "option space " << space->getName()
+                  << " already added.");
+    }
+    spaces4_.insert(std::pair<std::string,
+                              OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
+    if (!space) {
+        isc_throw(InvalidOptionSpace,
+                  "provided option space object is NULL.");
+    }
+    OptionSpaceCollection::iterator it = spaces6_.find(space->getName());
+    if (it != spaces6_.end()) {
+        isc_throw(InvalidOptionSpace, "option space " << space->getName()
+                  << " already added.");
+    }
+    spaces6_.insert(std::pair<std::string,
+                              OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
+                     const std::string& option_space) {
+    // @todo we need better validation of the provided option space name here.
+    // This will be implemented when #2313 is merged.
+    if (option_space.empty()) {
+        isc_throw(BadValue, "option space name must not be empty");
+    } else if (!def) {
+        // Option definition must point to a valid object.
+        isc_throw(MalformedOptionDefinition, "option definition must not be NULL");
+
+    } else if (getOptionDef(option_space, def->getCode())) {
+        // Option definition must not be overriden.
+        isc_throw(DuplicateOptionDefinition, "option definition already added"
+                  << " to option space " << option_space);
+
+    } else if ((option_space == "dhcp4" &&
+                LibDHCP::isStandardOption(Option::V4, def->getCode())) ||
+               (option_space == "dhcp6" &&
+                LibDHCP::isStandardOption(Option::V6, def->getCode()))) {
+        // We must not override standard (assigned) option. The standard options
+        // belong to dhcp4 or dhcp6 option space.
+        isc_throw(BadValue, "unable to override definition of option '"
+                  << def->getCode() << "' in standard option space '"
+                  << option_space << "'.");
+
+    }
+    // Get existing option definitions for the option space.
+    OptionDefContainerPtr defs = getOptionDefs(option_space);
+    // getOptionDefs always returns a valid pointer to
+    // the container. Let's make an assert to make sure.
+    assert(defs);
+    // Actually add the new definition.
+    defs->push_back(def);
+    option_def_spaces_[option_space] = defs;
+}
+
+OptionDefContainerPtr
+CfgMgr::getOptionDefs(const std::string& option_space) const {
+    // @todo Validate the option space once the #2313 is implemented.
+
+    // Get all option definitions for the particular option space.
+    const OptionDefsMap::const_iterator& defs =
+        option_def_spaces_.find(option_space);
+    // If there are no option definitions for the particular option space
+    // then return empty container.
+    if (defs == option_def_spaces_.end()) {
+        return (OptionDefContainerPtr(new OptionDefContainer()));
+    }
+    // If option definitions found, return them.
+    return (defs->second);
+}
+
+OptionDefinitionPtr
+CfgMgr::getOptionDef(const std::string& option_space,
+                     const uint16_t option_code) const {
+    // @todo Validate the option space once the #2313 is implemented.
+
+    // Get a reference to option definitions for a particular option space.
+    OptionDefContainerPtr defs = getOptionDefs(option_space);
+    // If there are no matching option definitions then return the empty pointer.
+    if (!defs || defs->empty()) {
+        return (OptionDefinitionPtr());
+    }
+    // If there are some option definitions for a particular option space
+    // use an option code to get the one we want.
+    const OptionDefContainerTypeIndex& idx = defs->get<1>();
+    const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+    // If there is no definition that matches option code, return empty pointer.
+    if (std::distance(range.first, range.second) == 0) {
+        return (OptionDefinitionPtr());
+    }
+    // If there is more than one definition matching an option code, return
+    // the first one. This should not happen because we check for duplicates
+    // when addOptionDef is called.
+    return (*range.first);
+}
+
 Subnet6Ptr
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 
@@ -42,6 +149,9 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     // The server does not need to have a global address (using just link-local
     // is ok for DHCPv6 server) from the pool it serves.
     if ((subnets6_.size() == 1) && hint.getAddress().to_v6().is_link_local()) {
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                  DHCPSRV_CFGMGR_ONLY_SUBNET6)
+                  .arg(subnets6_[0]->toText()).arg(hint.toText());
         return (subnets6_[0]);
     }
 
@@ -49,11 +159,16 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
         if ((*subnet)->inRange(hint)) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET6)
+                      .arg((*subnet)->toText()).arg(hint.toText());
             return (*subnet);
         }
     }
 
     // sorry, we don't support that subnet
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET6)
+              .arg(hint.toText());
     return (Subnet6Ptr());
 }
 
@@ -65,6 +180,8 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
 void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// other already defined subnet.
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+              .arg(subnet->toText());
     subnets6_.push_back(subnet);
 }
 
@@ -80,6 +197,9 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
     // The server does not need to have a global address (using just link-local
     // is ok for DHCPv6 server) from the pool it serves.
     if (subnets4_.size() == 1) {
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                  DHCPSRV_CFGMGR_ONLY_SUBNET4)
+                  .arg(subnets4_[0]->toText()).arg(hint.toText());
         return (subnets4_[0]);
     }
 
@@ -87,25 +207,38 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
     for (Subnet4Collection::iterator subnet = subnets4_.begin();
          subnet != subnets4_.end(); ++subnet) {
         if ((*subnet)->inRange(hint)) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET4)
+                      .arg((*subnet)->toText()).arg(hint.toText());
             return (*subnet);
         }
     }
 
     // sorry, we don't support that subnet
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET4)
+              .arg(hint.toText());
     return (Subnet4Ptr());
 }
 
 void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// other already defined subnet.
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
+              .arg(subnet->toText());
     subnets4_.push_back(subnet);
 }
 
+void CfgMgr::deleteOptionDefs() {
+    option_def_spaces_.clear();
+}
+
 void CfgMgr::deleteSubnets4() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET4);
     subnets4_.clear();
 }
 
 void CfgMgr::deleteSubnets6() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET6);
     subnets6_.clear();
 }
 
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index ac1b3f5..c1f1dd6 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -17,6 +17,8 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/option_space.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <util/buffer.h>
@@ -64,7 +66,6 @@ namespace dhcp {
 /// Parameter inheritance is likely to be implemented in configuration handling
 /// routines, so there is no storage capability in a global scope for
 /// subnet-specific parameters.
-///
 /// @todo: Implement Subnet4 support (ticket #2237)
 /// @todo: Implement option definition support
 /// @todo: Implement parameter inheritance
@@ -77,6 +78,71 @@ public:
     /// accessing it.
     static CfgMgr& instance();
 
+    /// @brief Add new option definition.
+    ///
+    /// @param def option definition to be added.
+    /// @param option_space name of the option space to add definition to.
+    ///
+    /// @throw isc::dhcp::DuplicateOptionDefinition when the particular
+    /// option definition already exists.
+    /// @throw isc::dhcp::MalformedOptionDefinition when the pointer to
+    /// an option definition is NULL.
+    /// @throw isc::BadValue when the option space name is empty or
+    /// when trying to override the standard option (in dhcp4 or dhcp6
+    /// option space).
+    void addOptionDef(const OptionDefinitionPtr& def,
+                      const std::string& option_space);
+
+    /// @brief Return option definitions for particular option space.
+    ///
+    /// @param option_space option space.
+    ///
+    /// @return pointer to the collection of option definitions for
+    /// the particular option space. The option collection is empty
+    /// if no option exists for the option space specified.
+    OptionDefContainerPtr
+    getOptionDefs(const std::string& option_space) const;
+
+    /// @brief Return option definition for a particular option space and code.
+    ///
+    /// @param option_space option space.
+    /// @param option_code option code.
+    ///
+    /// @return an option definition or NULL pointer if option definition
+    /// has not been found.
+    OptionDefinitionPtr getOptionDef(const std::string& option_space,
+                                     const uint16_t option_code) const;
+
+    /// @brief Adds new DHCPv4 option space to the collection.
+    ///
+    /// @param space option space to be added.
+    ///
+    /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+    /// has been specified.
+    void addOptionSpace4(const OptionSpacePtr& space);
+
+    /// @brief Adds new DHCPv6 option space to the collection.
+    ///
+    /// @param space option space to be added.
+    ///
+    /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+    /// has been specified.
+    void addOptionSpace6(const OptionSpacePtr& space);
+
+    /// @brief Return option spaces for DHCPv4.
+    ///
+    /// @return A collection of option spaces.
+    const OptionSpaceCollection& getOptionSpaces4() const {
+        return (spaces4_);
+    }
+
+    /// @brief Return option spaces for DHCPv6.
+    ///
+    /// @return A collection of option spaces.
+    const OptionSpaceCollection& getOptionSpaces6() const {
+        return (spaces6_);
+    }
+
     /// @brief get IPv6 subnet by address
     ///
     /// Finds a matching subnet, based on an address. This can be used
@@ -86,6 +152,8 @@ public:
     ///    (for directly connected clients)
     ///
     /// @param hint an address that belongs to a searched subnet
+    ///
+    /// @return a subnet object
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
 
     /// @brief get IPv6 subnet by interface-id
@@ -93,12 +161,19 @@ public:
     /// Another possibility to find a subnet is based on interface-id.
     ///
     /// @param interface_id content of interface-id option returned by a relay
+    ///
+    /// @return a subnet object
     /// @todo This method is not currently supported.
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
 
     /// @brief adds an IPv6 subnet
+    ///
+    /// @param subnet new subnet to be added.
     void addSubnet6(const Subnet6Ptr& subnet);
 
+    /// @brief Delete all option definitions.
+    void deleteOptionDefs();
+
     /// @todo: Add subnet6 removal routines. Currently it is not possible
     /// to remove subnets. The only case where subnet6 removal would be
     /// needed is a dynamic server reconfiguration - a use case that is not
@@ -125,6 +200,8 @@ public:
     ///    (for directly connected clients)
     ///
     /// @param hint an address that belongs to a searched subnet
+    ///
+    /// @return a subnet object
     Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
 
     /// @brief adds a subnet4
@@ -141,6 +218,7 @@ public:
     /// 192.0.2.0/23 and 192.0.2.0/24 the same subnet or is it something
     /// completely new?
     void deleteSubnets4();
+
 protected:
 
     /// @brief Protected constructor.
@@ -169,6 +247,25 @@ protected:
     /// pattern will use calling inRange() method on each subnet until
     /// a match is found.
     Subnet4Collection subnets4_;
+
+private:
+
+    /// A map containing option definitions for various option spaces.
+    /// They key of this map is the name of the option space. The
+    /// value is the the option container holding option definitions
+    /// for the particular option space.
+    typedef std::map<std::string, OptionDefContainerPtr> OptionDefsMap;
+
+    /// A map containing option definitions for different option spaces.
+    /// The map key holds an option space name.
+    OptionDefsMap option_def_spaces_;
+
+    /// @brief Container for defined DHCPv6 option spaces.
+    OptionSpaceCollection spaces6_;
+
+    /// @brief Container for defined DHCPv4 option spaces.
+    OptionSpaceCollection spaces4_;
+
 };
 
 } // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/dbaccess_parser.cc b/src/lib/dhcpsrv/dbaccess_parser.cc
new file mode 100644
index 0000000..93b7870
--- /dev/null
+++ b/src/lib/dhcpsrv/dbaccess_parser.cc
@@ -0,0 +1,100 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/dbaccess_parser.h>
+
+#include <boost/foreach.hpp>
+
+#include <map>
+#include <string>
+#include <utility>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+typedef map<string, ConstElementPtr> ConfigPairMap;
+typedef pair<string, ConstElementPtr> ConfigPair;
+typedef map<string, string> StringPairMap;
+typedef pair<string, string> StringPair;
+
+// Parse the configuration and check that the various keywords are consistent.
+void
+DbAccessParser::build(isc::data::ConstElementPtr config_value) {
+
+    // To cope with incremental updates, the strategy is:
+    // 1. Take a copy of the stored keyword/value pairs.
+    // 2. Update the copy with the passed keywords.
+    // 3. Perform validation checks on the updated keyword/value pairs.
+    // 4. If all is OK, update the stored keyword/value pairs.
+    // 5. Construct the updated database access string.
+
+    // 1. Take a copy of the stored keyword/value pairs.
+    map<string, string> values_copy = values_;
+
+    // 2. Update the copy with the passed keywords.
+    BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
+        values_copy[param.first] = param.second->stringValue();
+    }
+
+    // 3. Perform validation checks on the updated set of keyword/values.
+    //
+    // a. Check if the "type" keyword exists and thrown an exception if not.
+    StringPairMap::const_iterator type_ptr = values_copy.find("type");
+    if (type_ptr == values_copy.end()) {
+        isc_throw(TypeKeywordMissing, "lease database access parameters must "
+                  "include the keyword 'type' to determine type of database "
+                  "to be accessed");
+    }
+
+    // b. Check if the 'type; keyword known and throw an exception if not.
+    string dbtype = type_ptr->second;
+    if ((dbtype != "memfile") && (dbtype != "mysql")) {
+        isc_throw(BadValue, "unknown backend database type: " << dbtype);
+    }
+
+    /// @todo Log a warning if the type is memfile and there are other keywords.
+    ///       This will be done when the module is moved to libdhcpsrv
+
+    // 4. If all is OK, update the stored keyword/value pairs.
+    values_ = values_copy;
+
+    // 5. Construct the updated database access string: omit keywords where
+    //    the value string is empty.
+    dbaccess_ = "";
+    BOOST_FOREACH(StringPair keyval, values_) {
+        if (!keyval.second.empty()) {
+
+            // Separate keyword/value pair from predecessor (if there is one).
+            if (! dbaccess_.empty()) {
+                dbaccess_ += std::string(" ");
+            }
+
+            // Add the keyword/value pair to the access string.
+            dbaccess_ += (keyval.first + std::string("=") + keyval.second);
+        }
+    }
+}
+
+// Commit the changes - reopen the database with the new parameters
+void
+DbAccessParser::commit() {
+    std::cout << "DB_ACCESS_PARSER_COMMIT: " << dbaccess_ << "\n";
+}
+
+};  // namespace dhcp
+};  // namespace isc
+
diff --git a/src/lib/dhcpsrv/dbaccess_parser.h b/src/lib/dhcpsrv/dbaccess_parser.h
new file mode 100644
index 0000000..235da04
--- /dev/null
+++ b/src/lib/dhcpsrv/dbaccess_parser.h
@@ -0,0 +1,115 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DBACCESS_PARSER_H
+#define DBACCESS_PARSER_H
+
+#include <cc/data.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when 'type' keyword is missing from string
+///
+/// This condition is checked, but should never occur because 'type' is marked
+/// as mandatory in the .spec file for the server.
+class TypeKeywordMissing : public isc::Exception {
+public:
+    TypeKeywordMissing(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Parse Lease Database Parameters
+///
+/// This class is the parser for the lease database configuration.  This is a
+/// map under the top-level "lease-database" element, and comprises a map of
+/// strings.
+///
+/// Only the "type" sub-element is mandatory: the remaining sub-elements 
+/// depend on the datbase chosen.
+class DbAccessParser: public DhcpConfigParser {
+public:
+
+    /// @brief Default constructor
+    ///
+    /// @param param_name Name of the configuration parameter being parsed.
+    DbAccessParser(const std::string& param_name)
+        : dbaccess_(), param_name_(param_name), values_()
+    {}
+
+    /// The destructor.
+    virtual ~DbAccessParser()
+    {}
+
+    /// @brief Prepare configuration value.
+    ///
+    /// Parses the set of strings forming the database access specification and
+    /// checks that all are OK.  In particular it checks:
+    ///
+    /// - "type" is "memfile" or "mysql"
+    /// - If "type" is "memfile", checks that no other values are present: if
+    ///   they are, logs a warning that they will be ignored.
+    ///
+    /// Once all has been validated, constructs the database access string
+    /// expected by the lease manager.
+    ///
+    /// @param config_value The configuration value for the "lease-database"
+    ///        identifier.
+    ///
+    /// @throw isc::BadValue The 'type' keyword contains an unknown database
+    ///        type.
+    /// @throw isc::dhcp::MissingTypeKeyword The 'type' keyword is missing from
+    ///        the list of database access keywords.
+    virtual void build(isc::data::ConstElementPtr config_value);
+
+    /// @brief Apply the prepared configuration value to the server.
+    ///
+    /// With the string validated, this closes the currently open database (if
+    /// any), then opens a database corresponding to the stored string.
+    ///
+    /// This method is expected to be called after \c build(), and only once.
+    /// The result is undefined otherwise.
+    virtual void commit();
+
+    /// @brief Factory method to create parser
+    ///
+    /// Creates an instance of this parser.
+    static DhcpConfigParser* factory(const std::string& param_name) {
+        return (new DbAccessParser(param_name));
+    }
+
+    /// @brief Get database access string
+    ///
+    /// Used in testing to check that the configuration information has been
+    /// parsed corrected.
+    std::string getDbAccessString() const {
+        return (dbaccess_);
+    }
+
+private:
+    std::string     dbaccess_;      ///< Database access string
+    std::string     param_name_;    ///< Parameter name
+    std::map<std::string, std::string> values_;
+                                    ///< Stored parameter values
+};
+
+};  // namespace dhcp
+};  // namespace isc
+
+
+#endif // DBACCESS_PARSER_H
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
new file mode 100644
index 0000000..7077552
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2013 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 DHCP_CONFIG_PARSER_H
+#define DHCP_CONFIG_PARSER_H
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration to DhcpConfigParser class.
+///
+/// It is only needed here to define types that are
+/// based on this class before the class definition.
+class DhcpConfigParser;
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
+
+/// @brief Collection of parsers.
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+/// @brief Base abstract class for all DHCP parsers
+///
+/// Each instance of a class derived from this class parses one specific config
+/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
+/// complex (e.g. a subnet). In such case, it is likely that a parser will
+/// spawn child parsers to parse child elements in the configuration.
+class DhcpConfigParser {
+    ///
+    /// @name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private to make it explicit that this is a
+    /// pure base class.
+    //@{
+private:
+
+    // Private construtor and assignment operator assures that nobody
+    // will be able to copy or assign a parser. There are no defined
+    // bodies for them.
+    DhcpConfigParser(const DhcpConfigParser& source);
+    DhcpConfigParser& operator=(const DhcpConfigParser& source);
+protected:
+    /// @brief The default constructor.
+    ///
+    /// This is intentionally defined as @c protected as this base class should
+    /// never be instantiated (except as part of a derived class).
+    DhcpConfigParser() {}
+public:
+    /// The destructor.
+    virtual ~DhcpConfigParser() {}
+    //@}
+
+    /// @brief Prepare configuration value.
+    ///
+    /// This method parses the "value part" of the configuration identifier
+    /// that corresponds to this derived class and prepares a new value to
+    /// apply to the server.
+    ///
+    /// This method must validate the given value both in terms of syntax
+    /// and semantics of the configuration, so that the server will be
+    /// validly configured at the time of @c commit().  Note: the given
+    /// configuration value is normally syntactically validated, but the
+    /// @c build() implementation must also expect invalid input.  If it
+    /// detects an error it may throw an exception of a derived class
+    /// of @c isc::Exception.
+    ///
+    /// Preparing a configuration value will often require resource
+    /// allocation.  If it fails, it may throw a corresponding standard
+    /// exception.
+    ///
+    /// This method is not expected to be called more than once in the
+    /// life of the object. Although multiple calls are not prohibited
+    /// by the interface, the behavior is undefined.
+    ///
+    /// @param config_value The configuration value for the identifier
+    /// corresponding to the derived class.
+    virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+    /// @brief Apply the prepared configuration value to the server.
+    ///
+    /// This method is expected to be exception free, and, as a consequence,
+    /// it should normally not involve resource allocation.
+    /// Typically it would simply perform exception free assignment or swap
+    /// operation on the value prepared in @c build().
+    /// In some cases, however, it may be very difficult to meet this
+    /// condition in a realistic way, while the failure case should really
+    /// be very rare.  In such a case it may throw, and, if the parser is
+    /// called via @c configureDhcp4Server(), the caller will convert the
+    /// exception as a fatal error.
+    ///
+    /// This method is expected to be called after @c build(), and only once.
+    /// The result is undefined otherwise.
+    virtual void commit() = 0;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // DHCP_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.cc b/src/lib/dhcpsrv/dhcpsrv_log.cc
new file mode 100644
index 0000000..6cbfb0d
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "dhcpsrv/dhcpsrv_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger dhcpsrv_logger("dhcpsrv");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
new file mode 100644
index 0000000..9b6350a
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -0,0 +1,66 @@
+// 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 DHCPSRV_LOG_H
+#define DHCPSRV_LOG_H
+
+#include <dhcpsrv/dhcpsrv_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief DHCP server library logging levels
+///
+/// Defines the levels used to output debug messages in the DHCP server
+/// library.  Note that higher numbers equate to more verbose (and detailed)
+/// output.
+
+/// @brief Traces normal operations
+///
+/// E.g. sending a query to the database etc.
+const int DHCPSRV_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+/// @brief Records the results of the lookups
+///
+/// Using the example of tracing queries from the backend database, this will
+/// just record the summary results.
+const int DHCPSRV_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+
+/// @brief Additional information
+///
+/// Record detailed tracing. This is generally reserved for tracing access to
+/// the lease database.
+const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+
+/// @brief Additional information
+///
+/// Record detailed (and verbose) data on the server.
+const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+///@}
+
+
+/// \brief DHCP server library 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 dhcpsrv_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOG_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
new file mode 100644
index 0000000..27f12fc
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -0,0 +1,231 @@
+# Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv4 subnet to its database.
+
+% DHCPSRV_CFGMGR_ADD_SUBNET6 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv6 subnet to its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv4
+subnets in its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET6 deleting all IPv6 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv6
+subnets in its database.
+
+% DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv4 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_NO_SUBNET6 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv6 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_INVALID_ACCESS invalid database access string: %1
+This is logged when an attempt has been made to parse a database access string
+and the attempt ended in error.  The access string in question - which
+should be of the form 'keyword=value keyword=value...' is included in
+the message.
+
+% DHCPSRV_MEMFILE_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_COMMIT commiting to memory file database
+The code has issued a commit call.  For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_DB opening memory file lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a memory file lease database.  The parameters of
+the connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MEMFILE_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease
+for the specified address from the memory file database for the specified
+address.
+
+% DHCPSRV_MEMFILE_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+client identification.
+
+% DHCPSRV_MEMFILE_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+hardware address.
+
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the memory file database for a client with the specified
+IAID (Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and client ID.
+
+% DHCPSRV_MEMFILE_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and hardware address.
+
+% DHCPSRV_MEMFILE_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the memory file database.
+
+% DHCPSRV_MEMFILE_ROLLBACK rolling back memory file database
+The code has issued a rollback call.  For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_COMMIT commiting to MySQl database
+The code has issued a commit call.  All outstanding transactions will be
+committed to the database.  Note that depending on the MySQL settings,
+the commital may not include a write to disk.
+
+% DHCPSRV_MYSQL_DB opening MySQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a MySQL lease database.  The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MYSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+client identification.
+
+% DHCPSRV_MYSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_MYSQL_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the MySQL database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_MYSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_MYSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the MySQL database.
+
+% DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database
+The code has issued a rollback call.  All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
+This is an error message, logged when an attempt has been made to access
+a database backend, but where no 'type' keyword has been included in
+the access string.  The access string (less any passwords) is included
+in the message.
+
+% DHCPSRV_UNKNOWN_DB unknown database type: %1
+The database access string specified a database type (given in the
+message) that is unknown to the software.  This is a configuration error.
diff --git a/src/lib/dhcpsrv/hwaddr.cc b/src/lib/dhcpsrv/hwaddr.cc
new file mode 100644
index 0000000..25716e0
--- /dev/null
+++ b/src/lib/dhcpsrv/hwaddr.cc
@@ -0,0 +1,42 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include <dhcpsrv/hwaddr.h>
+
+#include <string>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+std::string
+hardwareAddressString(const HWAddr& hwaddr) {
+    std::ostringstream stream;
+
+    for (size_t i = 0; i < hwaddr.size(); ++i) {
+        if (i > 0) {
+            stream << ":";
+        }
+        stream << std::setw(2) << std::hex << std::setfill('0')
+               << static_cast<unsigned int>(hwaddr[i]);
+    }
+
+    return (stream.str());
+}
+
+};  // namespace dhcp
+};  // namespace isc
diff --git a/src/lib/dhcpsrv/hwaddr.h b/src/lib/dhcpsrv/hwaddr.h
new file mode 100644
index 0000000..776f37b
--- /dev/null
+++ b/src/lib/dhcpsrv/hwaddr.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HWADDR_H
+#define HWADDR_H
+
+#include <string>
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Hardware Address
+typedef std::vector<uint8_t> HWAddr;
+
+/// @brief Produce string representation of hardware address
+///
+/// Returns a string containing the hardware address. This is only used for
+/// logging.
+///
+/// @todo Create a "hardware address" class of which this will be a member.
+///
+/// @param hwaddr Hardware address to convert to string form
+///
+/// @return String form of the hardware address.
+std::string
+hardwareAddressString(const HWAddr& hwaddr);
+
+};  // namespace dhcp
+};  // namespace isc
+
+#endif // HWADDR_H
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index b3a605d..996fd58 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -63,7 +63,19 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
 }
 
 std::string
-Lease6::toText() {
+Lease4::toText() const {
+    ostringstream stream;
+
+    stream << "Address:       " << addr_.toText() << "\n"
+           << "Valid life:    " << valid_lft_ << "\n"
+           << "Cltt:          " << cltt_ << "\n"
+           << "Subnet ID:     " << subnet_id_ << "\n";
+
+    return (stream.str());
+}
+
+std::string
+Lease6::toText() const {
     ostringstream stream;
 
     stream << "Type:          " << static_cast<int>(type_) << " (";
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 3fe11e6..375a586 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -18,6 +18,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
+#include <dhcpsrv/hwaddr.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 
@@ -25,6 +26,7 @@
 #include <boost/shared_ptr.hpp>
 
 #include <fstream>
+#include <iostream>
 #include <map>
 #include <string>
 #include <utility>
@@ -61,8 +63,6 @@
 /// Nevertheless, we hope to have failover protocol eventually implemented in
 /// the Kea.
 
-#include <iostream>
-
 namespace isc {
 namespace dhcp {
 
@@ -222,12 +222,17 @@ struct Lease4 {
           comments_()
     {}
 
-    /// @brief Default Constructor
+    /// @brief Default constructor
     ///
     /// Initialize fields that don't have a default constructor.
     Lease4() : addr_(0), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false)
     {}
 
+    /// @brief Convert lease to printable form
+    ///
+    /// @return Textual represenation of lease data
+    std::string toText() const;
+
     /// @brief Compare two leases for equality
     ///
     /// @param other lease6 object with which to compare
@@ -377,7 +382,7 @@ struct Lease6 {
     /// @brief Convert Lease6 to Printable Form
     ///
     /// @return String form of the lease
-    std::string toText();
+    std::string toText() const;
 
     /// @brief returns true if the lease is expired
     /// @return true if the lease is expired
@@ -417,9 +422,6 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
 /// see the documentation of those classes for details.
 class LeaseMgr {
 public:
-    /// Client hardware address
-    typedef std::vector<uint8_t> HWAddr;
-
     /// Database configuration parameter map
     typedef std::map<std::string, std::string> ParameterMap;
 
@@ -474,7 +476,7 @@ public:
     /// @param hwaddr hardware address of the client
     ///
     /// @return lease collection
-    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
+    virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const = 0;
 
     /// @brief Returns existing IPv4 leases for specified hardware address
     ///        and a subnet
@@ -486,7 +488,7 @@ public:
     /// @param subnet_id identifier of the subnet that lease must belong to
     ///
     /// @return a pointer to the lease (or NULL if a lease is not found)
-    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+    virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
                                 SubnetID subnet_id) const = 0;
 
     /// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc
index 47c53ed..9fd276d 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.cc
+++ b/src/lib/dhcpsrv/lease_mgr_factory.cc
@@ -14,6 +14,7 @@
 
 #include "config.h"
 
+#include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 #ifdef HAVE_MYSQL
@@ -43,17 +44,17 @@ LeaseMgrFactory::getLeaseMgrPtr() {
 }
 
 LeaseMgr::ParameterMap
-LeaseMgrFactory::parse(const std::string& dbconfig) {
+LeaseMgrFactory::parse(const std::string& dbaccess) {
     LeaseMgr::ParameterMap mapped_tokens;
 
-    if (!dbconfig.empty()) {
+    if (!dbaccess.empty()) {
         vector<string> tokens;
 
         // We need to pass a string to is_any_of, not just char*. Otherwise
         // there are cryptic warnings on Debian6 running g++ 4.4 in
         // /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
         // array bounds"
-        boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
+        boost::split(tokens, dbaccess, boost::is_any_of(string("\t ")));
         BOOST_FOREACH(std::string token, tokens) {
             size_t pos = token.find("=");
             if (pos != string::npos) {
@@ -61,6 +62,7 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
                 string value = token.substr(pos + 1);
                 mapped_tokens.insert(make_pair(name, value));
             } else {
+                LOG_ERROR(dhcpsrv_logger, DHCPSRV_INVALID_ACCESS).arg(dbaccess);
                 isc_throw(InvalidParameter, "Cannot parse " << token
                           << ", expected format is name=value");
             }
@@ -70,31 +72,70 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
     return (mapped_tokens);
 }
 
+std::string
+LeaseMgrFactory::redactedAccessString(const LeaseMgr::ParameterMap& parameters) {
+    // Reconstruct the access string: start of with an empty string, then
+    // work through all the parameters in the original string and add them.
+    std::string access;
+    for (LeaseMgr::ParameterMap::const_iterator i = parameters.begin();
+         i != parameters.end(); ++i) {
+
+        // Separate second and subsequent tokens are preceded by a space.
+        if (!access.empty()) {
+            access += " ";
+        }
+
+        // Append name of parameter...
+        access += i->first;
+        access += "=";
+
+        // ... and the value, except in the case of the password, where a
+        // redacted value is appended.
+        if (i->first == std::string("password")) {
+            access += "*****";
+        } else {
+            access += i->second;
+        }
+    }
+
+    return (access);
+}
+
 void
-LeaseMgrFactory::create(const std::string& dbconfig) {
+LeaseMgrFactory::create(const std::string& dbaccess) {
     const std::string type = "type";
 
+    // Parse the access string and create a redacted string for logging.
+    LeaseMgr::ParameterMap parameters = parse(dbaccess);
+    std::string redacted = redactedAccessString(parameters);
+
     // Is "type" present?
-    LeaseMgr::ParameterMap parameters = parse(dbconfig);
     if (parameters.find(type) == parameters.end()) {
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
         isc_throw(InvalidParameter, "Database configuration parameters do not "
                   "contain the 'type' keyword");
     }
 
+
     // Yes, check what it is.
 #ifdef HAVE_MYSQL
     if (parameters[type] == string("mysql")) {
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_DB)
+            .arg(redacted);
         getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
         return;
     }
 #endif
     if (parameters[type] == string("memfile")) {
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MEMFILE_DB)
+            .arg(redacted);
         getLeaseMgrPtr().reset(new Memfile_LeaseMgr(parameters));
         return;
     }
 
     // Get here on no match
-    isc_throw(InvalidType, "Database configuration parameter 'type' does "
+    LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+    isc_throw(InvalidType, "Database access parameter 'type' does "
               "not specify a supported database backend");
 }
 
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h
index 338c941..9218cf5 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.h
+++ b/src/lib/dhcpsrv/lease_mgr_factory.h
@@ -66,21 +66,21 @@ public:
     /// @note When called, the current lease manager is <b>always</b> destroyed
     ///       and a new one created - even if the parameters are the same.
     ///
-    /// dbconfig is a generic way of passing parameters. Parameters are passed
+    /// dbaccess is a generic way of passing parameters. Parameters are passed
     /// in the "name=value" format, separated by spaces.  The data MUST include
     /// a keyword/value pair of the form "type=dbtype" giving the database
     /// type, e.q. "mysql" or "sqlite3".
     ///
-    /// @param dbconfig Database configuration parameters.  These are in
-    ///        the form of "keyword=value" pairs, separated by spaces. These
-    ///        are back-end specific, although must include the "type" keyword
-    ///        which gives the backend in use.
+    /// @param dbaccess Database access parameters.  These are in the form of
+    ///        "keyword=value" pairs, separated by spaces. They are backend-
+    ///        -end specific, although must include the "type" keyword which
+    ///        gives the backend in use.
     ///
-    /// @throw isc::InvalidParameter dbconfig string does not contain the "type"
+    /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
     ///        keyword.
-    /// @throw isc::dhcp::InvalidType The "type" keyword in dbconfig does not
+    /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
     ///        identify a supported backend.
-    static void create(const std::string& dbconfig);
+    static void create(const std::string& dbaccess);
 
     /// @brief Destroy lease manager
     ///
@@ -89,7 +89,7 @@ public:
     /// lease manager is available.
     static void destroy();
 
-    /// @brief Return Current Lease Manager
+    /// @brief Return current lease manager
     ///
     /// Returns an instance of the "current" lease manager.  An exception
     /// will be thrown if none is available.
@@ -98,15 +98,26 @@ public:
     ///        create() to create one before calling this method.
     static LeaseMgr& instance();
 
-    /// @brief Parse Database Parameters
+    /// @brief Parse database access string
     ///
     /// Parses the string of "keyword=value" pairs and separates them
     /// out into the map.
     ///
-    /// @param dbconfig Database configuration string
+    /// @param dbaccess Database access string.
     ///
     /// @return std::map<std::string, std::string> Map of keyword/value pairs.
-    static LeaseMgr::ParameterMap parse(const std::string& dbconfig);
+    static LeaseMgr::ParameterMap parse(const std::string& dbaccess);
+
+    /// @brief Redact database access string
+    ///
+    /// Takes the database parameters and returns a database access string
+    /// passwords replaced by asterisks. This string is used in log messages.
+    ///
+    /// @param parameters Database access parameters (output of "parse").
+    ///
+    /// @return Redacted database access string.
+    static std::string redactedAccessString(
+            const LeaseMgr::ParameterMap& parameters);
 
 private:
     /// @brief Hold pointer to lease manager
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index 79cf1c7..6b2c847 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/memfile_lease_mgr.h>
 
 #include <iostream>
@@ -27,11 +28,16 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
 Memfile_LeaseMgr::~Memfile_LeaseMgr() {
 }
 
-bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
+bool Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
     return (false);
 }
 
 bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
+
     if (getLease6(lease->addr_)) {
         // there is a lease with specified address already
         return (false);
@@ -40,30 +46,48 @@ bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
     return (true);
 }
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(
+        const isc::asiolink::IOAddress& addr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
+
     return (Lease4Ptr());
 }
 
-Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_HWADDR).arg(hardwareAddressString(hwaddr));
+
     return (Lease4Collection());
 }
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
-                                      SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
+                                      SubnetID subnet_id) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
+              .arg(hardwareAddressString(hwaddr));
     return (Lease4Ptr());
 }
 
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& clientid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_CLIENTID).arg(clientid.toText());
+    return (Lease4Collection());
+}
 
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
-                                      SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& clientid,
+                                      SubnetID subnet_id) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
+              .arg(clientid.toText());
     return (Lease4Ptr());
 }
 
-Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
-    return (Lease4Collection());
-}
+Lease6Ptr Memfile_LeaseMgr::getLease6(
+        const isc::asiolink::IOAddress& addr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_ADDR6).arg(addr.toText());
 
-Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
     Lease6Storage::iterator l = storage6_.find(addr);
     if (l == storage6_.end()) {
         return (Lease6Ptr());
@@ -72,12 +96,20 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) cons
     }
 }
 
-Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& duid,
+                                             uint32_t iaid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_IAID_DUID).arg(iaid).arg(duid.toText());
+
     return (Lease6Collection());
 }
 
 Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
                                       SubnetID subnet_id) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
+              .arg(iaid).arg(subnet_id).arg(duid.toText());
+
     /// @todo: Slow, naive implementation. Write it using additional indexes
     for (Lease6Storage::iterator l = storage6_.begin(); l != storage6_.end(); ++l) {
         if ( (*((*l)->duid_) == duid) &&
@@ -89,14 +121,22 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
     return (Lease6Ptr());
 }
 
-void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
+
 }
 
-void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
+
 
 }
 
 bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
     if (addr.isV4()) {
         // V4 not implemented yet
         return (false);
@@ -122,8 +162,11 @@ std::string Memfile_LeaseMgr::getDescription() const {
 
 void
 Memfile_LeaseMgr::commit() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
 }
 
 void
 Memfile_LeaseMgr::rollback() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MEMFILE_ROLLBACK);
 }
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 268b722..85813ec 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -15,6 +15,7 @@
 #ifndef MEMFILE_LEASE_MGR_H
 #define MEMFILE_LEASE_MGR_H
 
+#include <dhcpsrv/hwaddr.h>
 #include <dhcpsrv/lease_mgr.h>
 
 #include <boost/multi_index/indexed_by.hpp>
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index ad6e66c..a174aa0 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -16,14 +16,15 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
+#include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
 
 #include <boost/static_assert.hpp>
 #include <mysql/mysqld_error.h>
 
-#include <algorithm>
 #include <iostream>
 #include <iomanip>
+#include <sstream>
 #include <string>
 #include <time.h>
 
@@ -193,6 +194,8 @@ TaggedStatement tagged_statements[] = {
 
 };  // Anonymous namespace
 
+
+
 namespace isc {
 namespace dhcp {
 
@@ -1112,6 +1115,9 @@ MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
 
 bool
 MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_ADD_ADDR4).arg(lease->addr_.toText());
+
     // Create the MYSQL_BIND array for the lease
     std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
 
@@ -1121,6 +1127,9 @@ MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
 
 bool
 MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_ADD_ADDR6).arg(lease->addr_.toText());
+
     // Create the MYSQL_BIND array for the lease
     std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
 
@@ -1257,6 +1266,9 @@ void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
 
 Lease4Ptr
 MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_ADDR4).arg(addr.toText());
+
     // Set up the WHERE clause value
     MYSQL_BIND inbind[1];
     memset(inbind, 0, sizeof(inbind));
@@ -1276,6 +1288,9 @@ MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
 
 Lease4Collection
 MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_HWADDR).arg(hardwareAddressString(hwaddr));
+
     // Set up the WHERE clause value
     MYSQL_BIND inbind[1];
     memset(inbind, 0, sizeof(inbind));
@@ -1303,6 +1318,10 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
 
 Lease4Ptr
 MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_SUBID_HWADDR)
+              .arg(subnet_id).arg(hardwareAddressString(hwaddr));
+
     // Set up the WHERE clause value
     MYSQL_BIND inbind[2];
     memset(inbind, 0, sizeof(inbind));
@@ -1334,6 +1353,9 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
 
 Lease4Collection
 MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_CLIENTID).arg(clientid.toText());
+
     // Set up the WHERE clause value
     MYSQL_BIND inbind[1];
     memset(inbind, 0, sizeof(inbind));
@@ -1355,6 +1377,10 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
 
 Lease4Ptr
 MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_SUBID_CLIENTID)
+              .arg(subnet_id).arg(clientid.toText());
+
     // Set up the WHERE clause value
     MYSQL_BIND inbind[2];
     memset(inbind, 0, sizeof(inbind));
@@ -1380,6 +1406,9 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
 
 Lease6Ptr
 MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_ADDR6).arg(addr.toText());
+
     // Set up the WHERE clause value
     MYSQL_BIND inbind[1];
     memset(inbind, 0, sizeof(inbind));
@@ -1403,6 +1432,8 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
 
 Lease6Collection
 MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_IAID_DUID).arg(iaid).arg(duid.toText());
 
     // Set up the WHERE clause value
     MYSQL_BIND inbind[2];
@@ -1444,6 +1475,9 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
 Lease6Ptr
 MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
                          SubnetID subnet_id) const {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+              .arg(iaid).arg(subnet_id).arg(duid.toText());
 
     // Set up the WHERE clause value
     MYSQL_BIND inbind[3];
@@ -1511,6 +1545,9 @@ void
 MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
     const StatementIndex stindex = UPDATE_LEASE4;
 
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_UPDATE_ADDR4).arg(lease->addr_.toText());
+
     // Create the MYSQL_BIND array for the data being updated
     std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
 
@@ -1533,6 +1570,9 @@ void
 MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
     const StatementIndex stindex = UPDATE_LEASE6;
 
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_UPDATE_ADDR6).arg(lease->addr_.toText());
+
     // Create the MYSQL_BIND array for the data being updated
     std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
 
@@ -1579,6 +1619,8 @@ MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
 
 bool
 MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_DELETE_ADDR).arg(addr.toText());
 
     // Set up the WHERE clause value
     MYSQL_BIND inbind[1];
@@ -1632,6 +1674,9 @@ std::pair<uint32_t, uint32_t>
 MySqlLeaseMgr::getVersion() const {
     const StatementIndex stindex = GET_VERSION;
 
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+              DHCPSRV_MYSQL_GET_VERSION);
+
     uint32_t    major;      // Major version number
     uint32_t    minor;      // Minor version number
 
@@ -1678,6 +1723,7 @@ MySqlLeaseMgr::getVersion() const {
 
 void
 MySqlLeaseMgr::commit() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
     if (mysql_commit(mysql_) != 0) {
         isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_));
     }
@@ -1686,6 +1732,7 @@ MySqlLeaseMgr::commit() {
 
 void
 MySqlLeaseMgr::rollback() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
     if (mysql_rollback(mysql_) != 0) {
         isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_));
     }
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 68b0e1c..2103731 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -15,6 +15,7 @@
 #ifndef MYSQL_LEASE_MGR_H
 #define MYSQL_LEASE_MGR_H
 
+#include <dhcpsrv/hwaddr.h>
 #include <dhcpsrv/lease_mgr.h>
 
 #include <boost/scoped_ptr.hpp>
diff --git a/src/lib/dhcpsrv/option_space.cc b/src/lib/dhcpsrv/option_space.cc
new file mode 100644
index 0000000..6036e54
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2012, 2013 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 <dhcpsrv/option_space.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+namespace isc {
+namespace dhcp {
+
+OptionSpace::OptionSpace(const std::string& name, const bool vendor_space)
+    : name_(name), vendor_space_(vendor_space) {
+    //  Check that provided option space name is valid.
+    if (!validateName(name_)) {
+        isc_throw(InvalidOptionSpace, "Invalid option space name "
+                  << name_);
+    }
+}
+
+bool
+OptionSpace::validateName(const std::string& name) {
+
+    using namespace boost::algorithm;
+
+    // Allowed characters are: lower or upper case letters, digits,
+    // underscores and hyphens. Empty option spaces are not allowed.
+    if (all(name, boost::is_from_range('a', 'z') ||
+            boost::is_from_range('A', 'Z') ||
+            boost::is_digit() ||
+            boost::is_any_of("-_")) &&
+        !name.empty() &&
+        // Hyphens and underscores are not allowed at the beginning
+        // and at the end of the option space name.
+        !all(find_head(name, 1), boost::is_any_of("-_")) &&
+        !all(find_tail(name, 1), boost::is_any_of("-_"))) {
+        return (true);
+
+    }
+    return (false);
+}
+
+OptionSpace6::OptionSpace6(const std::string& name)
+    : OptionSpace(name),
+      enterprise_number_(0) {
+}
+
+OptionSpace6::OptionSpace6(const std::string& name,
+                           const uint32_t enterprise_number)
+    : OptionSpace(name, true),
+      enterprise_number_(enterprise_number) {
+}
+
+void
+OptionSpace6::setVendorSpace(const uint32_t enterprise_number) {
+    enterprise_number_ = enterprise_number;
+    OptionSpace::setVendorSpace();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/option_space.h b/src/lib/dhcpsrv/option_space.h
new file mode 100644
index 0000000..9eebd76
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.h
@@ -0,0 +1,189 @@
+// Copyright (C) 2012, 2013 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 OPTION_SPACE_H
+#define OPTION_SPACE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid option space
+/// is specified.
+class InvalidOptionSpace : public Exception {
+public:
+    InvalidOptionSpace(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// OptionSpace forward declaration.
+class OptionSpace;
+/// A pointer to OptionSpace object.
+typedef boost::shared_ptr<OptionSpace> OptionSpacePtr;
+/// A collection of option spaces.
+typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection;
+
+/// @brief DHCP option space.
+///
+/// This class represents single option space. The option spaces are used
+/// to group DHCP options having unique option codes. The special type
+/// of the option space is so called "vendor specific option space".
+/// It groups sub-options being sent within Vendor Encapsulated Options.
+/// For DHCPv4 it is the option with code 43. The option spaces are
+/// assigned to option instances represented by isc::dhcp::Option and
+/// other classes derived from it. Each particular option may belong to
+/// multiple option spaces.
+/// This class may be used to represent any DHCPv4 option space. If the
+/// option space is to group DHCPv4 Vendor Encapsulated Options then
+/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace
+/// or the argument passed to the constructor. In theory, this class can
+/// be also used to represent non-vendor specific DHCPv6 option space
+/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6
+/// class should be used instead.
+///
+/// @note this class is intended to be used to represent DHCPv4 option
+/// spaces only. However, it hasn't been called OptionSpace4 (that would
+/// suggest that it is specific to DHCPv4) because it can be also
+/// used to represent some DHCPv6 option spaces and is a base class
+/// for \ref OptionSpace6. Thus, if one declared the container as follows:
+/// @code
+/// std::vector<OptionSpace4> container;
+/// @endcode
+/// it would suggest that the container holds DHCPv4 option spaces while
+/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6
+/// object could be upcast to OptionSpace4. This confusion does not appear
+/// when OptionSpace is used as a name for the base class.
+class OptionSpace {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param name option space name.
+    /// @param vendor_space boolean value that indicates that the object
+    /// describes the vendor specific option space.
+    ///
+    /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+    /// contains invalid characters or is empty. This constructor uses
+    /// \ref validateName function to check that the specified name is
+    /// correct.
+    OptionSpace(const std::string& name, const bool vendor_space = false);
+
+    /// @brief Return option space name.
+    ///
+    /// @return option space name.
+    const std::string& getName() const { return (name_); }
+
+    /// @brief Mark option space as non-vendor space.
+    void clearVendorSpace() {
+        vendor_space_ = false;
+    }
+
+    /// @brief Check if option space is vendor specific.
+    ///
+    /// @return boolean value that indicates if the object describes
+    /// the vendor specific option space.
+    bool isVendorSpace() const { return (vendor_space_); }
+
+    /// @brief Mark option space as vendor specific.
+    void setVendorSpace() {
+        vendor_space_ = true;
+    }
+
+    /// @brief Checks that the provided option space name is valid.
+    ///
+    /// It is expected that option space name consists of upper or
+    /// lower case letters or digits. Also, it may contain underscores
+    /// or dashes. Other characters are prohibited. The empty option
+    /// space names are invalid.
+    ///
+    /// @param name option space name to be validated.
+    ///
+    /// @return true if the option space is valid, else it returns false.
+    static bool validateName(const std::string& name);
+
+private:
+    std::string name_;  ///< Holds option space name.
+
+    bool vendor_space_; ///< Is this the vendor space?
+
+};
+
+/// @brief DHCPv6 option space with enterprise number assigned.
+///
+/// This class extends the base class with the support for enterprise numbers.
+/// The enterprise numbers are assigned by IANA to various organizations
+/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific
+/// Information Options (VSIO). For more information refer to RFC3315.
+/// All option spaces that group VSIO options must have enterprise number
+/// set. It can be set using a constructor or \ref setVendorSpace function.
+/// The extra functionality of this class (enterprise numbers) allows to
+/// represent DHCPv6 vendor-specific option spaces but this class is also
+/// intended to be used for all other DHCPv6 option spaces. That way all
+/// DHCPv6 option spaces can be stored in the container holding OptionSpace6
+/// objects. Also, it is easy to mark vendor-specific option space as non-vendor
+/// specific option space (and the other way around) without a need to cast
+/// between OptionSpace and OptionSpace6 types.
+class OptionSpace6 : public OptionSpace {
+public:
+
+    /// @brief Constructor for non-vendor-specific options.
+    ///
+    /// This constructor marks option space as non-vendor specific.
+    ///
+    /// @param name option space name.
+    ///
+    /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+    /// contains invalid characters or is empty. This constructor uses
+    /// \ref OptionSpace::validateName function to check that the specified
+    /// name is correct.
+    OptionSpace6(const std::string& name);
+
+    /// @brief Constructor for vendor-specific options.
+    ///
+    /// This constructor marks option space as vendor specific and sets
+    /// enterprise number to a given value.
+    ///
+    /// @param name option space name.
+    /// @param enterprise_number enterprise number.
+    ///
+    /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+    /// contains invalid characters or is empty. This constructor uses
+    /// \ref OptionSpace::validateName function to check that the specified
+    /// name is correct.
+    OptionSpace6(const std::string& name, const uint32_t enterprise_number);
+
+    /// @brief Return enterprise number for the option space.
+    ///
+    /// @return enterprise number.
+    uint32_t getEnterpriseNumber() const { return (enterprise_number_); }
+
+    /// @brief Mark option space as vendor specific.
+    ///
+    /// @param enterprise_number enterprise number.
+    void setVendorSpace(const uint32_t enterprise_number);
+
+private:
+    
+    uint32_t enterprise_number_; ///< IANA assigned enterprise number.
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_SPACE_H
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 3f8733f..3f31130 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -44,14 +44,63 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
 }
 
 void
-Subnet::addOption(OptionPtr& option, bool persistent /* = false */) {
+Subnet::addOption(OptionPtr& option, bool persistent,
+                  const std::string& option_space) {
+    // @todo Once the #2313 is merged we need to use the OptionSpace object to
+    // validate the option space name here. For now, let's check that the name
+    // is not empty as the empty namespace has a special meaning here - it is
+    // returned when desired namespace is not found when getOptions is called.
+    if (option_space.empty()) {
+        isc_throw(isc::BadValue, "option space name must not be empty");
+    }
     validateOption(option);
-    options_.push_back(OptionDescriptor(option, persistent));
+
+    OptionContainerPtr container = getOptionDescriptors(option_space);
+    // getOptionDescriptors is expected to return the pointer to the
+    // valid container. Let's make sure it does by performing an assert.
+    assert(container);
+    // Actually add the new descriptor.
+    container->push_back(OptionDescriptor(option, persistent));
+    option_spaces_[option_space] = container;
 }
 
 void
 Subnet::delOptions() {
-    options_.clear();
+    option_spaces_.clear();
+}
+
+Subnet::OptionContainerPtr
+Subnet::getOptionDescriptors(const std::string& option_space) const {
+    // Search the map to get the options container for the particular
+    // option space.
+    const OptionSpacesPtr::const_iterator& options =
+        option_spaces_.find(option_space);
+    // If the option space has not been found it means that no option
+    // has been configured for this option space yet. Thus we have to
+    // return an empty container to the caller.
+    if (options == option_spaces_.end()) {
+        // The default constructor creates an empty container.
+        return (OptionContainerPtr(new OptionContainer()));
+    }
+    // We found some option container for the option space specified.
+    // Let's return a const reference to it.
+    return (options->second);
+}
+
+Subnet::OptionDescriptor
+Subnet::getOptionDescriptor(const std::string& option_space,
+                            const uint16_t option_code) {
+    OptionContainerPtr options = getOptionDescriptors(option_space);
+    if (!options || options->empty()) {
+        return (OptionDescriptor(false));
+    }
+    const OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeRange& range = idx.equal_range(option_code);
+    if (std::distance(range.first, range.second) == 0) {
+        return (OptionDescriptor(false));
+    }
+
+    return (*range.first);
 }
 
 std::string Subnet::toText() const {
@@ -77,8 +126,8 @@ void Subnet4::addPool4(const Pool4Ptr& pool) {
 
     if (!inRange(first_addr) || !inRange(last_addr)) {
         isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
-                  << " does not belong in this (" << prefix_ << "/" << prefix_len_
-                  << ") subnet4");
+                  << " does not belong in this (" << prefix_.toText() << "/"
+                  << static_cast<int>(prefix_len_) << ") subnet4");
     }
 
     /// @todo: Check that pools do not overlap
@@ -148,10 +197,10 @@ void Subnet6::addPool6(const Pool6Ptr& pool) {
 
     if (!inRange(first_addr) || !inRange(last_addr)) {
         isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
-                  << " does not belong in this (" << prefix_ << "/" << prefix_len_
+                  << ") does not belong in this (" << prefix_.toText()
+                  << "/" << static_cast<int>(prefix_len_)
                   << ") subnet6");
     }
-
     /// @todo: Check that pools do not overlap
 
     pools_.push_back(pool);
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index c7d7ac7..442369f 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -78,6 +78,9 @@ public:
             : option(OptionPtr()), persistent(persist) {};
     };
 
+    /// A pointer to option descriptor.
+    typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
+
     /// @brief Extractor class to extract key with another key.
     ///
     /// This class solves the problem of accessing index key values
@@ -198,6 +201,8 @@ public:
         >
     > OptionContainer;
 
+    // Pointer to the OptionContainer object.
+    typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
     /// Type of the index #1 - option type.
     typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
     /// Pair of iterators to represent the range of options having the
@@ -216,9 +221,11 @@ public:
     /// @param option option instance.
     /// @param persistent if true, send an option regardless if client
     /// requested it or not.
+    /// @param option_space name of the option space to add an option to.
     ///
     /// @throw isc::BadValue if invalid option provided.
-    void addOption(OptionPtr& option, bool persistent = false);
+    void addOption(OptionPtr& option, bool persistent,
+                   const std::string& option_space);
 
     /// @brief Delete all options configured for the subnet.
     void delOptions();
@@ -252,14 +259,24 @@ public:
         return (t2_);
     }
 
-    /// @brief Return a collection of options.
+    /// @brief Return a collection of option descriptors.
     ///
-    /// @return reference to collection of options configured for a subnet.
-    /// The returned reference is valid as long as the Subnet object which
-    /// returned it still exists.
-    const OptionContainer& getOptions() const {
-        return (options_);
-    }
+    /// @param option_space name of the option space.
+    ///
+    /// @return pointer to collection of options configured for a subnet.
+    OptionContainerPtr
+    getOptionDescriptors(const std::string& option_space) const;
+
+    /// @brief Return single option descriptor.
+    ///
+    /// @param option_space name of the option space.
+    /// @param option_code code of the option to be returned.
+    ///
+    /// @return option descriptor found for the specified option space
+    /// and option code.
+    OptionDescriptor
+    getOptionDescriptor(const std::string& option_space,
+                        const uint16_t option_code);
 
     /// @brief returns the last address that was tried from this pool
     ///
@@ -353,9 +370,6 @@ protected:
     /// @brief a tripet (min/default/max) holding allowed valid lifetime values
     Triplet<uint32_t> valid_;
 
-    /// @brief a collection of DHCP options configured for a subnet.
-    OptionContainer options_;
-
     /// @brief last allocated address
     ///
     /// This is the last allocated address that was previously allocated from
@@ -366,6 +380,15 @@ protected:
     /// that purpose it should be only considered a help that should not be
     /// fully trusted.
     isc::asiolink::IOAddress last_allocated_;
+
+private:
+
+    /// Container holding options grouped by option space names.
+    typedef std::map<std::string, OptionContainerPtr> OptionSpacesPtr;
+
+    /// @brief a collection of DHCP option spaces holding options
+    /// configured for a subnet.
+    OptionSpacesPtr option_spaces_;
 };
 
 /// @brief A configuration holder for IPv4 subnet.
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 11e1d75..2f77518 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -30,12 +30,15 @@ libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += hwaddr_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 if HAVE_MYSQL
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
 endif
+libdhcpsrv_unittests_SOURCES += option_space_unittest.cc
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc
 libdhcpsrv_unittests_SOURCES += schema_copy.h
 libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
@@ -62,6 +65,8 @@ endif
 
 libdhcpsrv_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 7b23bcf..ab1d586 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -42,10 +42,173 @@ public:
     }
 
     ~CfgMgrTest() {
+        CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
+        CfgMgr::instance().deleteOptionDefs();
     }
 };
 
+// This test verifies that multiple option definitions can be added
+// under different option spaces.
+TEST_F(CfgMgrTest, getOptionDefs) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // Create a set of option definitions with codes between 100 and 109.
+    for (uint16_t code = 100; code < 110; ++code) {
+        std::ostringstream option_name;
+        // Option name is unique, e.g. option-100, option-101 etc.
+        option_name << "option-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        // Add option definition to "isc" option space.
+        // Option codes are not duplicated so expect no error
+        // when adding them.
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+    }
+
+    // Create a set of option definitions with codes between 105 and 114 and
+    // add them to the different option space.
+    for (uint16_t code = 105; code < 115; ++code) {
+        std::ostringstream option_name;
+        option_name << "option-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+    }
+
+    // Sanity check that all 10 option definitions are there.
+    OptionDefContainerPtr option_defs1 = cfg_mgr.getOptionDefs("isc");
+    ASSERT_TRUE(option_defs1);
+    ASSERT_EQ(10, option_defs1->size());
+
+    // Iterate over all option definitions and check that they have
+    // valid codes. Also, their order should be the same as they
+    // were added (codes 100-109).
+    uint16_t code = 100;
+    for (OptionDefContainer::const_iterator it = option_defs1->begin();
+         it != option_defs1->end(); ++it, ++code) {
+        OptionDefinitionPtr def(*it);
+        ASSERT_TRUE(def);
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Sanity check that all 10 option definitions are there.
+    OptionDefContainerPtr option_defs2 = cfg_mgr.getOptionDefs("abcde");
+    ASSERT_TRUE(option_defs2);
+    ASSERT_EQ(10, option_defs2->size());
+
+    // Check that the option codes are valid.
+    code = 105;
+    for (OptionDefContainer::const_iterator it = option_defs2->begin();
+         it != option_defs2->end(); ++it, ++code) {
+        OptionDefinitionPtr def(*it);
+        ASSERT_TRUE(def);
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Let's make one more check that the empty set is returned when
+    // invalid option space is used.
+    OptionDefContainerPtr option_defs3 = cfg_mgr.getOptionDefs("non-existing");
+    ASSERT_TRUE(option_defs3);
+    EXPECT_TRUE(option_defs3->empty());
+}
+
+// This test verifies that single option definition is correctly
+// returned with getOptionDef function.
+TEST_F(CfgMgrTest, getOptionDef) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // Create a set of option definitions with codes between 100 and 109.
+    for (uint16_t code = 100; code < 110; ++code) {
+        std::ostringstream option_name;
+        // Option name is unique, e.g. option-100, option-101 etc.
+        option_name << "option-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        // Add option definition to "isc" option space.
+        // Option codes are not duplicated so expect no error
+        // when adding them.
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+    }
+
+    // Create a set of option definitions with codes between 105 and 114 and
+    // add them to the different option space.
+    for (uint16_t code = 105; code < 115; ++code) {
+        std::ostringstream option_name;
+        option_name << "option-other-" << code;
+        OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+                                                     "uint16"));
+        ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+    }
+
+    // Try to get option definitions one by one using all codes
+    // that we expect to be there.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionDefinitionPtr def = cfg_mgr.getOptionDef("isc", code);
+        ASSERT_TRUE(def);
+        // Check that the option name is in the format of 'option-[code]'.
+        // That way we make sure that the options that have the same codes
+        // within different option spaces are different.
+        std::ostringstream option_name;
+        option_name << "option-" << code;
+        EXPECT_EQ(option_name.str(), def->getName());
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Check that the option codes are valid.
+    for (uint16_t code = 105; code < 115; ++code) {
+        OptionDefinitionPtr def = cfg_mgr.getOptionDef("abcde", code);
+        ASSERT_TRUE(def);
+        // Check that the option name is in the format of 'option-other-[code]'.
+        // That way we make sure that the options that have the same codes
+        // within different option spaces are different.
+        std::ostringstream option_name;
+        option_name << "option-other-" << code;
+        EXPECT_EQ(option_name.str(), def->getName());
+
+        EXPECT_EQ(code, def->getCode());
+    }
+
+    // Check that an option definition can be added to the standard
+    // (dhcp4 and dhcp6) option spaces when the option code is not
+    // reserved by the standard option.
+    OptionDefinitionPtr def6(new OptionDefinition("option-foo", 79, "uint16"));
+    EXPECT_NO_THROW(cfg_mgr.addOptionDef(def6, "dhcp6"));
+
+    OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222, "uint16"));
+    EXPECT_NO_THROW(cfg_mgr.addOptionDef(def4, "dhcp4"));
+
+    // Try to query the option definition from an non-existing
+    // option space and expect NULL pointer.
+    OptionDefinitionPtr def = cfg_mgr.getOptionDef("non-existing", 56);
+    EXPECT_FALSE(def);
+
+    // Try to get the non-existing option definition from an
+    // existing option space.
+    EXPECT_FALSE(cfg_mgr.getOptionDef("isc", 56));
+
+}
+
+// This test verifies that the function that adds new option definition
+// throws exceptions when arguments are invalid.
+TEST_F(CfgMgrTest, addOptionDefNegative) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // The option code 65 is reserved for standard options either in
+    // DHCPv4 or DHCPv6. Thus we expect that adding an option to this
+    // option space fails.
+    OptionDefinitionPtr def(new OptionDefinition("option-foo", 65, "uint16"));
+
+    // Try reserved option space names.
+    ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp4"), isc::BadValue);
+    ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp6"), isc::BadValue);
+    // Try empty option space name.
+    ASSERT_THROW(cfg_mgr.addOptionDef(def, ""), isc::BadValue);
+    // Try NULL option definition.
+    ASSERT_THROW(cfg_mgr.addOptionDef(OptionDefinitionPtr(), "isc"),
+                 isc::dhcp::MalformedOptionDefinition);
+    // Try adding option definition twice and make sure that it
+    // fails on the second attempt.
+    ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+    EXPECT_THROW(cfg_mgr.addOptionDef(def, "isc"), DuplicateOptionDefinition);
+}
 
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
@@ -56,8 +219,8 @@ TEST_F(CfgMgrTest, subnet4) {
     Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
 
-    // there shouldn't be any subnet configured at this stage
-    EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+    // There shouldn't be any subnet configured at this stage
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
 
     cfg_mgr.addSubnet4(subnet1);
 
@@ -74,12 +237,17 @@ TEST_F(CfgMgrTest, subnet4) {
     EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
 
     // Try to find an address that does not belong to any subnet
-    EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+
+    // Check that deletion of the subnets works.
+    cfg_mgr.deleteSubnets4();
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
+    EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
 }
 
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
-
 TEST_F(CfgMgrTest, subnet6) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
@@ -87,8 +255,8 @@ TEST_F(CfgMgrTest, subnet6) {
     Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
     Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
 
-    // there shouldn't be any subnet configured at this stage
-    EXPECT_EQ( Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("2000::1")));
+    // There shouldn't be any subnet configured at this stage
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1")));
 
     cfg_mgr.addSubnet6(subnet1);
 
@@ -104,12 +272,79 @@ TEST_F(CfgMgrTest, subnet6) {
 
     EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
     EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
-    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1")));
 
+    // Check that deletion of the subnets works.
     cfg_mgr.deleteSubnets6();
-    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
-    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
-    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("200::123")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123")));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
+}
+
+// This test verifies that new DHCPv4 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace4) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Create some option spaces.
+    OptionSpacePtr space1(new OptionSpace("isc", false));
+    OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+    // Add option spaces with different names and expect they
+    // are accepted.
+    ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space1));
+    ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space2));
+
+    // Validate that the option spaces have been added correctly.
+    const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces4();
+
+    ASSERT_EQ(2, spaces.size());
+    EXPECT_FALSE(spaces.find("isc") == spaces.end());
+    EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+    // Create another option space with the name that duplicates
+    // the existing option space.
+    OptionSpacePtr space3(new OptionSpace("isc", true));
+    // Expect that the duplicate option space is rejected.
+    ASSERT_THROW(
+        cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
+    );
+
+    // @todo decode if a duplicate vendor space is allowed.
+}
+
+// This test verifies that new DHCPv6 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace6) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Create some option spaces.
+    OptionSpacePtr space1(new OptionSpace("isc", false));
+    OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+    // Add option spaces with different names and expect they
+    // are accepted.
+    ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space1));
+    ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space2));
+
+    // Validate that the option spaces have been added correctly.
+    const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces6();
+
+    ASSERT_EQ(2, spaces.size());
+    EXPECT_FALSE(spaces.find("isc") == spaces.end());
+    EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+    // Create another option space with the name that duplicates
+    // the existing option space.
+    OptionSpacePtr space3(new OptionSpace("isc", true));
+    // Expect that the duplicate option space is rejected.
+    ASSERT_THROW(
+        cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
+    );
+
+    // @todo decide if a duplicate vendor space is allowed.
 }
 
 } // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
new file mode 100644
index 0000000..6c22fad
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
@@ -0,0 +1,395 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcpsrv/dbaccess_parser.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <config/ccsession.h>
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+/// @brief Database Access Parser test fixture class
+class DbAccessParserTest : public ::testing::Test {
+public:
+    /// @ Build JSON String
+    ///
+    /// Given a array of "const char*" strings representing in order, keyword,
+    /// value, keyword, value, ... and terminated by a NULL, return a string
+    /// that represents the JSON map for the keywords and values.
+    ///
+    /// E.g. given the array of strings: alpha, one, beta, two, NULL, it would
+    /// return the string '{ "alpha": "one", "beta": "two" }'
+    ///
+    /// @param keyval Array of "const char*" strings in the order keyword,
+    ///        value, keyword, value ...  A NULL entry terminates the list.
+    ///
+    /// @return JSON map for the keyword value array.
+    std::string toJson(const char* keyval[]) {
+        const std::string quote = "\"";
+        const std::string colon = ":";
+        const std::string space = " ";
+
+        string result = "{ ";
+
+        for (size_t i = 0; keyval[i] != NULL; i+= 2) {
+            // Get the value.  This should not be NULL.  As ASSERT_NE will
+            // cause a return - which gives compilation problems as a return
+            // statement is expected to return a string - use EXPECT_NE and
+            // explicitly return if the expected array is incorrect.
+            EXPECT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
+                "Supplied reference keyword/value list does not contain values "
+                "for all keywords";
+            if (keyval[i + 1] == NULL) {
+                return (std::string(""));
+            }
+
+            // Add the separating comma if not the first.
+            if (i != 0) {
+                result += ", ";
+            }
+
+            // Add the keyword and value - make sure that they are quoted.
+            result += quote + keyval[i] + quote + colon + space +
+                      quote + keyval[i + 1] + quote;
+        }
+
+        // Add the terminating brace
+        result += " }";
+
+        return (result);
+    }
+
+    /// @brief Check for Keywords
+    ///
+    /// Takes a database access string and checks it against a list of keywords
+    /// and values.  It checks that:
+    ///
+    /// a. Every keyword in the string appears once and only once in the
+    ///    list.
+    /// b. Every keyword in the list appears in the string.
+    /// c. Every keyword's value is the same as that in the string.
+    ///
+    /// To parse the access string, we use the parsing function in the
+    /// DHCP lease manager.
+    ///
+    /// @param trace_string String that will be used to set the value of a
+    ///        SCOPED_TRACE for this call.
+    /// @param dbaccess Database access string to check
+    /// @param keyval Array of "const char*" strings in the order keyword,
+    ///        value, keyword, value ...  A NULL entry terminates the list.
+    void checkAccessString(const char* trace_string, std::string& dbaccess,
+                           const char* keyval[]) {
+        SCOPED_TRACE(trace_string);
+
+        // Construct a map of keyword value pairs.  Check that no keyword
+        // is repeated.
+        map<string, string> expected;
+        size_t expected_count = 0;
+        for (size_t i = 0; keyval[i] != NULL; i += 2) {
+            // Get the value.  This should not be NULL
+            ASSERT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
+                "Supplied reference keyword/value list does not contain values "
+                "for all keywords";
+            expected[keyval[i]] = keyval[i + 1];
+
+            // One more keyword processed
+            ++expected_count;
+        }
+
+        // Check no duplicates in the supplied keywords
+        ASSERT_EQ(expected_count, expected.size()) << 
+            "Supplied reference keyword/value list contains duplicate keywords";
+
+        // Split the database access string.
+        const LeaseMgr::ParameterMap dbamap = LeaseMgrFactory::parse(dbaccess);
+
+        // It should have the same number keyword value pairs as the
+        EXPECT_EQ(expected_count, dbamap.size());
+
+        // Check that the keywords and keyword values are the same: loop
+        // through the keywords in the database access string.
+        for (LeaseMgr::ParameterMap::const_iterator actual = dbamap.begin();
+             actual != dbamap.end(); ++actual) {
+
+            // Does the keyword exist in the set of expected keywords?
+            map<string, string>::iterator corresponding =
+                expected.find(actual->first);
+            ASSERT_TRUE(corresponding != expected.end());
+
+            // Keyword exists, is the value the same?
+            EXPECT_EQ(corresponding->second, actual->second);
+        }
+    }
+};
+
+// Check that the parser works with a simple configuration.
+TEST_F(DbAccessParserTest, validTypeMemfile) {
+    const char* config[] = {"type", "memfile",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    DbAccessParser parser("lease-database");
+    EXPECT_NO_THROW(parser.build(json_elements));
+    string dbaccess = parser.getDbAccessString();
+    checkAccessString("Valid memfile", dbaccess, config);
+}
+
+// Check that the parser works with a valid MySQL configuration
+TEST_F(DbAccessParserTest, validTypeMysql) {
+    const char* config[] = {"type",     "mysql",
+                            "host",     "erewhon",
+                            "user",     "kea",
+                            "password", "keapassword",
+                            "name",     "keatest",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    DbAccessParser parser("lease-database");
+    EXPECT_NO_THROW(parser.build(json_elements));
+    string dbaccess = parser.getDbAccessString();
+    checkAccessString("Valid mysql", dbaccess, config);
+}
+
+// A missing 'type' keyword should cause an exception to be thrown.
+TEST_F(DbAccessParserTest, missingTypeKeyword) {
+    const char* config[] = {"host",     "erewhon",
+                            "user",     "kea",
+                            "password", "keapassword",
+                            "name",     "keatest",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    DbAccessParser parser("lease-database");
+    EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
+}
+
+// If the value of the "type" keyword is unknown, a BadValue exception should
+// be thrown.
+TEST_F(DbAccessParserTest, badTypeKeyword) {
+    const char* config[] = {"type",     "invalid",
+                            "host",     "erewhon",
+                            "user",     "kea",
+                            "password", "keapassword",
+                            "name",     "keatest",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    DbAccessParser parser("lease-database");
+    EXPECT_THROW(parser.build(json_elements), BadValue);
+}
+
+// Check that the factory function works.
+TEST_F(DbAccessParserTest, factory) {
+    const char* config[] = {"type", "memfile",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    // Check that the parser is built through the factory.
+    boost::scoped_ptr<DhcpConfigParser> parser(
+        DbAccessParser::factory("lease-database"));
+    EXPECT_NO_THROW(parser->build(json_elements));
+
+    // Access the "raw" parser.
+    DbAccessParser* dbap = dynamic_cast<DbAccessParser*>(parser.get());
+    EXPECT_NE(static_cast<DbAccessParser*>(NULL), dbap);
+    string dbaccess = dbap->getDbAccessString();
+    checkAccessString("Valid mysql", dbaccess, config);
+}
+
+// Check reconfiguration.  Checks that incremental changes applied to the
+// database configuration are incremental.
+TEST_F(DbAccessParserTest, incrementalChanges) {
+    const char* config1[] = {"type", "memfile",
+                             NULL};
+
+    // Applying config2 will cause a wholesale change.
+    const char* config2[] = {"type",     "mysql",
+                             "host",     "erewhon",
+                             "user",     "kea",
+                             "password", "keapassword",
+                             "name",     "keatest",
+                             NULL};
+
+    // Applying incremental2 should cause a change to config3.
+    const char* incremental2[] = {"user",     "me",
+                                  "password", "meagain",
+                                  NULL};
+    const char* config3[] = {"type",     "mysql",
+                             "host",     "erewhon",
+                             "user",     "me",
+                             "password", "meagain",
+                             "name",     "keatest",
+                             NULL};
+
+    // incremental3 will cause an exception.  There should be no change
+    // to the returned value.
+    const char* incremental3[] = {"type",     "invalid",
+                                  "user",     "you",
+                                  "password", "youagain",
+                                  NULL};
+
+    // incremental4 is a compatible change and should cause a transition
+    // to config4.
+    const char* incremental4[] = {"user",     "them",
+                                  "password", "themagain",
+                                  NULL};
+    const char* config4[] = {"type",     "mysql",
+                             "host",     "erewhon",
+                             "user",     "them",
+                             "password", "themagain",
+                             "name",     "keatest",
+                             NULL};
+
+    DbAccessParser parser("lease-database");
+
+    // First configuration string should cause a representation of that string
+    // to be held.
+    string json_config = toJson(config1);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_NO_THROW(parser.build(json_elements));
+    string dbaccess = parser.getDbAccessString();
+    checkAccessString("Initial configuration", dbaccess, config1);
+
+    // Applying a wholesale change will cause the access string to change
+    // to a representation of the new configuration.
+    json_config = toJson(config2);
+    json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_NO_THROW(parser.build(json_elements));
+    dbaccess = parser.getDbAccessString();
+    checkAccessString("Subsequent configuration", dbaccess, config2);
+
+    // Applying an incremental change will cause the representation to change
+    // incrementally.
+    json_config = toJson(incremental2);
+    json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_NO_THROW(parser.build(json_elements));
+    dbaccess = parser.getDbAccessString();
+    checkAccessString("Incremental configuration", dbaccess, config3);
+
+    // Applying the next incremental change should cause an exception to be
+    // thrown and there be no change to the access string.
+    json_config = toJson(incremental3);
+    json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_THROW(parser.build(json_elements), BadValue);
+    dbaccess = parser.getDbAccessString();
+    checkAccessString("Incompatible incremental change", dbaccess, config3);
+
+    // Applying an incremental change will cause the representation to change
+    // incrementally.
+    json_config = toJson(incremental4);
+    json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_NO_THROW(parser.build(json_elements));
+    dbaccess = parser.getDbAccessString();
+    checkAccessString("Compatible incremental change", dbaccess, config4);
+}
+
+// Check reconfiguration and that elements set to an empty string are omitted.
+TEST_F(DbAccessParserTest, emptyStringOmission) {
+    const char* config1[] = {"type", "memfile",
+                             NULL};
+
+    // Applying config2 will cause a wholesale change.
+    const char* config2[] = {"type",     "mysql",
+                             "host",     "erewhon",
+                             "user",     "kea",
+                             "password", "keapassword",
+                             "name",     "keatest",
+                             NULL};
+
+    // Applying incremental2 should cause a change to config3.
+    const char* incremental2[] = {"user",     "me",
+                                  "password", "",
+                                  "host",     "",
+                                  NULL};
+
+    const char* config3[] = {"type",     "mysql",
+                             "user",     "me",
+                             "name",     "keatest",
+                             NULL};
+
+    DbAccessParser parser("lease-database");
+
+    // First configuration string should cause a representation of that string
+    // to be held.
+    string json_config = toJson(config1);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_NO_THROW(parser.build(json_elements));
+    string dbaccess = parser.getDbAccessString();
+    checkAccessString("Initial configuration", dbaccess, config1);
+
+    // Applying a wholesale change will cause the access string to change
+    // to a representation of the new configuration.
+    json_config = toJson(config2);
+    json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_NO_THROW(parser.build(json_elements));
+    dbaccess = parser.getDbAccessString();
+    checkAccessString("Subsequent configuration", dbaccess, config2);
+
+    // Applying an incremental change will cause the representation to change
+    // incrementally.
+    json_config = toJson(incremental2);
+    json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    EXPECT_NO_THROW(parser.build(json_elements));
+    dbaccess = parser.getDbAccessString();
+    checkAccessString("Incremental configuration", dbaccess, config3);
+}
+
+};  // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/hwaddr_unittest.cc b/src/lib/dhcpsrv/tests/hwaddr_unittest.cc
new file mode 100644
index 0000000..f6b769d
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/hwaddr_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/hwaddr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc::dhcp;
+
+namespace {
+
+TEST(HwaddrTest, stringConversion) {
+
+    // Check that an empty vector returns an appropriate string
+    HWAddr hwaddr;
+    std::string result = hardwareAddressString(hwaddr);
+    EXPECT_EQ(std::string(""), result);
+
+    // ... that a single-byte string is OK
+    hwaddr.push_back(0xc3);
+    result = hardwareAddressString(hwaddr);
+    EXPECT_EQ(std::string("c3"), result);
+
+    // ... and that a multi-byte string works
+    hwaddr.push_back(0x7);
+    hwaddr.push_back(0xa2);
+    hwaddr.push_back(0xe8);
+    hwaddr.push_back(0x42);
+    result = hardwareAddressString(hwaddr);
+    EXPECT_EQ(std::string("c3:07:a2:e8:42"), result);
+}
+
+};  // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
index e343c44..9924476 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
@@ -16,6 +16,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <exceptions/exceptions.h>
 
 #include <gtest/gtest.h>
 
@@ -37,16 +38,136 @@ public:
     }
 };
 
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
+// This test checks that a database access string can be parsed correctly.
 TEST_F(LeaseMgrFactoryTest, parse) {
 
-    std::map<std::string, std::string> parameters = LeaseMgrFactory::parse(
-        "param1=value1 param2=value2 param3=value3");
+    LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(
+        "user=me password=forbidden name=kea somethingelse= type=mysql");
 
-    EXPECT_EQ("value1", parameters["param1"]);
-    EXPECT_EQ("value2", parameters["param2"]);
-    EXPECT_TRUE(parameters.find("type") == parameters.end());
+    EXPECT_EQ(5, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("forbidden", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+    EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST_F(LeaseMgrFactoryTest, parseInvalid) {
+
+    // No tokens in the string, so we expect no parameters
+    std::string invalid = "";
+    LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(invalid);
+    EXPECT_EQ(0, parameters.size());
+
+    // With spaces, there are some tokens so we expect invalid parameter
+    // as there are no equals signs.
+    invalid = "   \t  ";
+    EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+    invalid = "   noequalshere  ";
+    EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+    // A single "=" is valid string, but is placed here as the result is
+    // expected to be nothing.
+    invalid = "=";
+    parameters = LeaseMgrFactory::parse(invalid);
+    EXPECT_EQ(1, parameters.size());
+    EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactConfigString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST_F(LeaseMgrFactoryTest, redactAccessString) {
+
+    LeaseMgr::ParameterMap parameters =
+        LeaseMgrFactory::parse("user=me password=forbidden name=kea type=mysql");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("forbidden", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+    parameters = LeaseMgrFactory::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringEmptyPassword) {
+
+    LeaseMgr::ParameterMap parameters =
+        LeaseMgrFactory::parse("user=me name=kea type=mysql password=");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+    parameters = LeaseMgrFactory::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // ... and again to check that the position of the empty password in the
+    // string does not matter.
+    parameters = LeaseMgrFactory::parse("user=me password= name=kea type=mysql");
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    redacted = LeaseMgrFactory::redactedAccessString(parameters);
+    parameters = LeaseMgrFactory::parse(redacted);
+
+    EXPECT_EQ(4, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("*****", parameters["password"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begion with.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringNoPassword) {
+
+    LeaseMgr::ParameterMap parameters =
+        LeaseMgrFactory::parse("user=me name=kea type=mysql");
+    EXPECT_EQ(3, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
+
+    // Redact the result.  To check, break the redacted string down into its
+    // components.
+    std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+    parameters = LeaseMgrFactory::parse(redacted);
+
+    EXPECT_EQ(3, parameters.size());
+    EXPECT_EQ("me", parameters["user"]);
+    EXPECT_EQ("kea", parameters["name"]);
+    EXPECT_EQ("mysql", parameters["type"]);
 }
 
 }; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/option_space_unittest.cc b/src/lib/dhcpsrv/tests/option_space_unittest.cc
new file mode 100644
index 0000000..f8d75c8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/option_space_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright (C) 2012, 2013 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 <config.h>
+
+#include <dhcpsrv/option_space.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+using namespace isc;
+
+namespace {
+
+// The purpose of this test is to verify that the constructor
+// creates an object with members initialized to correct values.
+TEST(OptionSpaceTest, constructor) {
+    // Create some option space.
+    OptionSpace space("isc", true);
+    EXPECT_EQ("isc", space.getName());
+    EXPECT_TRUE(space.isVendorSpace());
+
+    // Create another object with different values
+    // to check that the values will change.
+    OptionSpace space2("abc", false);
+    EXPECT_EQ("abc", space2.getName());
+    EXPECT_FALSE(space2.isVendorSpace());
+
+    // Verify that constructor throws exception if invalid
+    // option space name is provided.
+    EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify that the vendor-space flag
+// can be overriden.
+TEST(OptionSpaceTest, setVendorSpace) {
+    OptionSpace space("isc", true);
+    EXPECT_EQ("isc", space.getName());
+    EXPECT_TRUE(space.isVendorSpace());
+
+    // Override the vendor space flag.
+    space.clearVendorSpace();
+    EXPECT_FALSE(space.isVendorSpace());
+}
+
+// The purpose of this test is to verify that the static function
+// to validate the option space name works correctly.
+TEST(OptionSpaceTest, validateName) {
+    // Positive test scenarios: letters, digits, dashes, underscores
+    // lower/upper case allowed.
+    EXPECT_TRUE(OptionSpace::validateName("abc"));
+    EXPECT_TRUE(OptionSpace::validateName("dash-allowed"));
+    EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed"));
+    EXPECT_TRUE(OptionSpace::validateName("underscore_allowed"));
+    EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed"));
+    EXPECT_TRUE(OptionSpace::validateName("digits0912"));
+    EXPECT_TRUE(OptionSpace::validateName("1234"));
+    EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
+
+    // Negative test scenarions: empty strings, dots, spaces are not
+    // allowed
+    EXPECT_FALSE(OptionSpace::validateName(""));
+    EXPECT_FALSE(OptionSpace::validateName(" "));
+    EXPECT_FALSE(OptionSpace::validateName(" isc "));
+    EXPECT_FALSE(OptionSpace::validateName("isc "));
+    EXPECT_FALSE(OptionSpace::validateName(" isc"));
+    EXPECT_FALSE(OptionSpace::validateName("isc with-space"));
+
+    // Hyphens and underscores are not allowed at the beginning
+    // and at the end of the option space name.
+    EXPECT_FALSE(OptionSpace::validateName("-isc"));
+    EXPECT_FALSE(OptionSpace::validateName("isc-"));
+    EXPECT_FALSE(OptionSpace::validateName("_isc"));
+    EXPECT_FALSE(OptionSpace::validateName("isc_"));
+
+    // Test other special characters
+    const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+                              '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'',
+                              '\\', '|', '<','>', ',', '.', '?', '~', '`' };
+    for (int i = 0; i < sizeof(specials); ++i) {
+        std::ostringstream stream;
+        // Concatenate valid option space name: "abc" with an invalid character.
+        // That way we get option space names like: "abc!", "abc$" etc. It is
+        // expected that the validating function fails form them.
+        stream << "abc" << specials[i];
+        EXPECT_FALSE(OptionSpace::validateName(stream.str()))
+            << "Test failed for special character '" << specials[i] << "'.";
+    }
+}
+
+// The purpose of this test is to verify that the constructors of the
+// OptionSpace6 class set the class members to correct values.
+TEST(OptionSpace6Test, constructor) {
+    // Create some option space and do not specify enterprise number.
+    // In such case the vendor space flag is expected to be
+    // set to false.
+    OptionSpace6 space1("abcd");
+    EXPECT_EQ("abcd", space1.getName());
+    EXPECT_FALSE(space1.isVendorSpace());
+
+    // Create an option space and specify an enterprise number. In this
+    // case the vendor space flag is expected to be set to true and the
+    // enterprise number should be set to a desired value.
+    OptionSpace6 space2("abcd", 2145);
+    EXPECT_EQ("abcd", space2.getName());
+    EXPECT_TRUE(space2.isVendorSpace());
+    EXPECT_EQ(2145, space2.getEnterpriseNumber());
+
+    // Verify that constructors throw an exception when invalid option
+    // space name has been specified.
+    EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace);
+    EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify an option space can be marked
+// vendor option space and enterprise number can be set.
+TEST(OptionSpace6Test, setVendorSpace) {
+    OptionSpace6 space("isc");
+    EXPECT_EQ("isc", space.getName());
+    EXPECT_FALSE(space.isVendorSpace());
+
+    // Mark it vendor option space and set enterprise id.
+    space.setVendorSpace(1234);
+    EXPECT_TRUE(space.isVendorSpace());
+    EXPECT_EQ(1234, space.getEnterpriseNumber());
+
+    // Override the enterprise number to make sure and make sure that
+    // the new number is returned by the object.
+    space.setVendorSpace(2345);
+    EXPECT_TRUE(space.isVendorSpace());
+    EXPECT_EQ(2345, space.getEnterpriseNumber());
+
+    // Clear the vendor option space flag.
+    space.clearVendorSpace();
+    EXPECT_FALSE(space.isVendorSpace());
+}
+
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 9ebef9c..2fc9f7e 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -115,13 +115,15 @@ TEST(Subnet4Test, addInvalidOption) {
     // Create option with invalid universe (V6 instead of V4).
     // Attempt to add this option should result in exception.
     OptionPtr option1(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-    EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+    EXPECT_THROW(subnet->addOption(option1, false, "dhcp4"),
+                 isc::BadValue);
 
     // Create NULL pointer option. Attempt to add NULL option
     // should result in exception.
     OptionPtr option2;
     ASSERT_FALSE(option2);
-    EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+    EXPECT_THROW(subnet->addOption(option2, false, "dhcp4"),
+                 isc::BadValue);
 }
 
 // This test verifies that inRange() and inPool() methods work properly.
@@ -261,26 +263,59 @@ TEST(Subnet6Test, addOptions) {
     // Differentiate options by their codes (100-109)
     for (uint16_t code = 100; code < 110; ++code) {
         OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-        ASSERT_NO_THROW(subnet->addOption(option));
+        ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+    }
+
+    // Add 7 options to another option space. The option codes partially overlap
+    // with option codes that we have added to dhcp6 option space.
+    for (uint16_t code = 105; code < 112; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(subnet->addOption(option, false, "isc"));
     }
 
     // Get options from the Subnet and check if all 10 are there.
-    Subnet::OptionContainer options = subnet->getOptions();
-    ASSERT_EQ(10, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(10, options->size());
 
-    // Validate codes of added options.
+    // Validate codes of options added to dhcp6 option space.
     uint16_t expected_code = 100;
-    for (Subnet::OptionContainer::const_iterator option_desc = options.begin();
-         option_desc != options.end(); ++option_desc) {
+    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option);
+        EXPECT_EQ(expected_code, option_desc->option->getType());
+        ++expected_code;
+    }
+
+    options = subnet->getOptionDescriptors("isc");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(7, options->size());
+
+    // Validate codes of options added to isc option space.
+    expected_code = 105;
+    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
         ASSERT_TRUE(option_desc->option);
         EXPECT_EQ(expected_code, option_desc->option->getType());
         ++expected_code;
     }
 
+    // Try to get options from a non-existing option space.
+    options = subnet->getOptionDescriptors("abcd");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+
+    // Delete options from all spaces.
     subnet->delOptions();
 
-    options = subnet->getOptions();
-    EXPECT_EQ(0, options.size());
+    // Make sure that all options have been removed.
+    options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+
+    options = subnet->getOptionDescriptors("isc");
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
 }
 
 TEST(Subnet6Test, addNonUniqueOptions) {
@@ -292,19 +327,19 @@ TEST(Subnet6Test, addNonUniqueOptions) {
         // In the inner loop we create options with unique codes (100-109).
         for (uint16_t code = 100; code < 110; ++code) {
             OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
-            ASSERT_NO_THROW(subnet->addOption(option));
+            ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
         }
     }
 
     // Sanity check that all options are there.
-    Subnet::OptionContainer options = subnet->getOptions();
-    ASSERT_EQ(20, options.size());
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_EQ(20, options->size());
 
     // Use container index #1 to get the options by their codes.
-    Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    Subnet::OptionContainerTypeIndex& idx = options->get<1>();
     // Look for the codes 100-109.
     for (uint16_t code = 100; code < 110; ++ code) {
-        // For each code we should get two instances of options.
+        // For each code we should get two instances of options->
         std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
                   Subnet::OptionContainerTypeIndex::const_iterator> range =
             idx.equal_range(code);
@@ -329,8 +364,8 @@ TEST(Subnet6Test, addNonUniqueOptions) {
 
     subnet->delOptions();
 
-    options = subnet->getOptions();
-    EXPECT_EQ(0, options.size());
+    options = subnet->getOptionDescriptors("dhcp6");
+    EXPECT_EQ(0, options->size());
 }
 
 TEST(Subnet6Test, addInvalidOption) {
@@ -342,13 +377,13 @@ TEST(Subnet6Test, addInvalidOption) {
     // Create option with invalid universe (V4 instead of V6).
     // Attempt to add this option should result in exception.
     OptionPtr option1(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
-    EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+    EXPECT_THROW(subnet->addOption(option1, false, "dhcp6"), isc::BadValue);
 
     // Create NULL pointer option. Attempt to add NULL option
     // should result in exception.
     OptionPtr option2;
     ASSERT_FALSE(option2);
-    EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+    EXPECT_THROW(subnet->addOption(option2, false, "dhcp6"), isc::BadValue);
 }
 
 TEST(Subnet6Test, addPersistentOption) {
@@ -367,24 +402,24 @@ TEST(Subnet6Test, addPersistentOption) {
         // and options with these codes will be flagged non-persistent.
         // Options with other codes will be flagged persistent.
         bool persistent = (code % 3) ? true : false;
-        ASSERT_NO_THROW(subnet->addOption(option, persistent));
+        ASSERT_NO_THROW(subnet->addOption(option, persistent, "dhcp6"));
     }
 
     // Get added options from the subnet.
-    Subnet::OptionContainer options = subnet->getOptions();
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
 
-    // options.get<2> returns reference to container index #2. This
+    // options->get<2> returns reference to container index #2. This
     // index is used to access options by the 'persistent' flag.
-    Subnet::OptionContainerPersistIndex& idx = options.get<2>();
+    Subnet::OptionContainerPersistIndex& idx = options->get<2>();
 
-    // Get all persistent options.
+    // Get all persistent options->
     std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
               Subnet::OptionContainerPersistIndex::const_iterator> range_persistent =
         idx.equal_range(true);
     // 3 out of 10 options have been flagged persistent.
     ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
 
-    // Get all non-persistent options.
+    // Get all non-persistent options->
     std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
               Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
         idx.equal_range(false);
@@ -393,8 +428,33 @@ TEST(Subnet6Test, addPersistentOption) {
 
     subnet->delOptions();
 
-    options = subnet->getOptions();
-    EXPECT_EQ(0, options.size());
+    options = subnet->getOptionDescriptors("dhcp6");
+    EXPECT_EQ(0, options->size());
+}
+
+TEST(Subnet6Test, getOptionDescriptor) {
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 56, 1, 2, 3, 4));
+
+    // Add 10 options to a "dhcp6" option space in the subnet.
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+    }
+
+    // Check that we can get each added option descriptor using
+    // individually.
+    for (uint16_t code = 100; code < 110; ++code) {
+        std::ostringstream stream;
+        // First, try the invalid option space name.
+        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("isc", code);
+        // Returned descriptor should contain NULL option ptr.
+        EXPECT_FALSE(desc.option);
+        // Now, try the valid option space.
+        desc = subnet->getOptionDescriptor("dhcp6", code);
+        // Test that the option code matches the expected code.
+        ASSERT_TRUE(desc.option);
+        EXPECT_EQ(code, desc.option->getType());
+    }
 }
 
 // This test verifies that inRange() and inPool() methods work properly.
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 8525842..3f6ae63 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -95,6 +95,7 @@ lib_LTLIBRARIES = libb10-dns++.la
 libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
 
 libb10_dns___la_SOURCES =
+libb10_dns___la_SOURCES += dns_fwd.h
 libb10_dns___la_SOURCES += edns.h edns.cc
 libb10_dns___la_SOURCES += exceptions.h exceptions.cc
 libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
@@ -127,6 +128,9 @@ libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
 libb10_dns___la_SOURCES += character_string.h character_string.cc
 libb10_dns___la_SOURCES += master_loader_callbacks.h master_loader_callbacks.cc
 libb10_dns___la_SOURCES += master_loader.h
+libb10_dns___la_SOURCES += rrset_collection_base.h
+libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
+libb10_dns___la_SOURCES += zone_checker.h zone_checker.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
@@ -156,6 +160,7 @@ libdns___includedir = $(includedir)/$(PACKAGE_NAME)/dns
 libdns___include_HEADERS = \
 	edns.h \
 	exceptions.h \
+	dns_fwd.h \
 	labelsequence.h \
 	message.h \
 	masterload.h \
@@ -170,8 +175,11 @@ libdns___include_HEADERS = \
 	rdata.h \
 	rrparamregistry.h \
 	rrset.h \
+	rrset_collection_base.h \
+	rrset_collection.h \
 	rrttl.h \
-	tsigkey.h
+	tsigkey.h \
+	zone_checker.h
 # Purposely not installing these headers:
 # name_internal.h: used only internally, and not actually DNS specific
 # rdata/*/detail/*.h: these are internal use only
diff --git a/src/lib/dns/dns_fwd.h b/src/lib/dns/dns_fwd.h
new file mode 100644
index 0000000..df71388
--- /dev/null
+++ b/src/lib/dns/dns_fwd.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_FWD_H
+#define DNS_FWD_H 1
+
+/// \file dns_fwd.h
+/// \brief Forward declarations for definitions of libdns++
+///
+/// This file provides a set of forward declarations for definitions commonly
+/// used in libdns++ to help minimize dependency when actual the definition
+/// is not necessary.
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class MasterLoader;
+class MasterLoaderCallbacks;
+class Message;
+class AbstractMessageRenderer;
+class MessageRenderer;
+class NSEC3Hash;
+class NSEC3HashCreator;
+class Opcode;
+class Question;
+class Rcode;
+namespace rdata {
+class Rdata;
+}
+class RRCollator;
+class RRClass;
+class RRType;
+class RRTTL;
+class AbstractRRset;
+class RdataIterator;
+class RRsetCollectionBase;
+class RRsetCollection;
+class Serial;
+class TSIGContext;
+class TSIGError;
+class TSIGKey;
+class TSIGKeyRing;
+class TSIGRecord;
+
+} // namespace dns
+} // namespace isc
+#endif  // DNS_FWD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 7ad6c0f..01bf671 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -219,28 +219,23 @@ private:
             // after the RR class below.
         }
 
-        boost::scoped_ptr<RRClass> rrclass;
-        try {
-            rrclass.reset(new RRClass(rrparam_token.getString()));
+        const MaybeRRClass rrclass =
+            RRClass::createFromText(rrparam_token.getString());
+        if (rrclass) {
+            if (*rrclass != zone_class_) {
+                isc_throw(InternalException, "Class mismatch: " << *rrclass <<
+                          " vs. " << zone_class_);
+            }
             rrparam_token = lexer_.getNextToken(MasterToken::STRING);
-        } catch (const InvalidRRClass&) {
-            // If it's not an rrclass here, use the zone's class.
-            rrclass.reset(new RRClass(zone_class_));
         }
 
         // If we couldn't parse TTL earlier in the stream (above), try
         // again at current location.
-        if (!explicit_ttl &&
-            setCurrentTTL(rrparam_token.getString())) {
+        if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) {
             explicit_ttl = true;
             rrparam_token = lexer_.getNextToken(MasterToken::STRING);
         }
 
-        if (*rrclass != zone_class_) {
-            isc_throw(InternalException, "Class mismatch: " << *rrclass <<
-                      "vs. " << zone_class_);
-        }
-
         // Return the current string token's value as the RRType.
         return (RRType(rrparam_token.getString()));
     }
@@ -398,7 +393,7 @@ private:
     shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
     const RRClass zone_class_;
     MasterLoaderCallbacks callbacks_;
-    AddRRCallback add_callback_;
+    const AddRRCallback add_callback_;
     boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
                                            // unspecified.  If NULL no default
                                            // is known.
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
index f9cc18b..e725595 100644
--- a/src/lib/dns/master_loader_callbacks.h
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -100,7 +100,7 @@ public:
     /// If the caller of the loader wants to abort, it is possible to throw
     /// from the callback, which aborts the load.
     void error(const std::string& source_name, size_t source_line,
-               const std::string& reason)
+               const std::string& reason) const
     {
         error_(source_name, source_line, reason);
     }
@@ -117,7 +117,7 @@ public:
     /// may be false positives), it is possible to throw from inside the
     /// callback.
     void warning(const std::string& source_name, size_t source_line,
-                 const std::string& reason)
+                 const std::string& reason) const
     {
         warning_(source_name, source_line, reason);
     }
@@ -133,7 +133,7 @@ public:
     static MasterLoaderCallbacks getNullCallbacks();
 
 private:
-    IssueCallback error_, warning_;
+    const IssueCallback error_, warning_;
 };
 
 }
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 6dd94b6..a221bfe 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -25,6 +25,9 @@ libb10_pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
 libb10_pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
 libb10_pydnspp_la_SOURCES += edns_python.cc edns_python.h
 libb10_pydnspp_la_SOURCES += message_python.cc message_python.h
+libb10_pydnspp_la_SOURCES += rrset_collection_python.cc
+libb10_pydnspp_la_SOURCES += rrset_collection_python.h
+libb10_pydnspp_la_SOURCES += zone_checker_python.cc zone_checker_python.h
 
 libb10_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -43,6 +46,8 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 EXTRA_DIST = tsigerror_python_inc.cc
 EXTRA_DIST += message_python_inc.cc
 EXTRA_DIST += nsec3hash_python_inc.cc
+EXTRA_DIST += rrset_collection_python_inc.cc
+EXTRA_DIST += zone_checker_python_inc.cc
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 6d1bd89..c75c737 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -50,12 +50,16 @@
 #include "rrset_python.h"
 #include "rrttl_python.h"
 #include "rrtype_python.h"
+#include "rrset_collection_python.h"
 #include "serial_python.h"
 #include "tsigerror_python.h"
 #include "tsigkey_python.h"
 #include "tsig_python.h"
 #include "tsig_rdata_python.h"
 #include "tsigrecord_python.h"
+#include "zone_checker_python.h"
+
+#include "zone_checker_python_inc.cc"
 
 using namespace isc::dns;
 using namespace isc::dns::python;
@@ -728,6 +732,11 @@ initModulePart_TSIGRecord(PyObject* mod) {
     return (true);
 }
 
+PyMethodDef methods[] = {
+    { "check_zone", internal::pyCheckZone, METH_VARARGS, dns_checkZone_doc },
+    { NULL, NULL, 0, NULL }
+};
+
 PyModuleDef pydnspp = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     "pydnspp",
@@ -737,13 +746,13 @@ PyModuleDef pydnspp = {
     "and OutputBuffer for instance), and others may be necessary, but "
     "were not up to now.",
     -1,
-    NULL,
+    methods,
     NULL,
     NULL,
     NULL,
     NULL
 };
-}
+} // unnamed namespace
 
 PyMODINIT_FUNC
 PyInit_pydnspp(void) {
@@ -864,5 +873,13 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_RRsetCollectionBase(mod)) {
+        return (NULL);
+    }
+
+    if (!initModulePart_RRsetCollection(mod)) {
+        return (NULL);
+    }
+
     return (mod);
 }
diff --git a/src/lib/dns/python/rrset_collection_python.cc b/src/lib/dns/python/rrset_collection_python.cc
new file mode 100644
index 0000000..df313f7
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.cc
@@ -0,0 +1,426 @@
+// Copyright (C) 2013  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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/name_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrset_collection.h>
+
+#include <string>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+// Import pydoc text
+#include "rrset_collection_python_inc.cc"
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+    PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+                 var_name, type_name, pobj->ob_type->tp_name);
+    return (NULL);
+}
+}
+
+//
+// RRsetCollectionBase
+//
+
+namespace {
+int
+RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
+    PyErr_SetString(PyExc_TypeError,
+                    "RRsetCollectionBase cannot be instantiated directly");
+    return (-1);
+}
+
+void
+RRsetCollectionBase_destroy(PyObject* po_self) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+
+    if (self->cppobj == NULL) {
+        PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
+                     "derived RRsetCollection class");
+        return (NULL);
+    }
+
+    try {
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        PyObject* po_rrtype;
+
+        if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+            if (!PyName_Check(po_name)) {
+                return (setTypeError(po_name, "name", "Name"));
+            }
+            if (!PyRRClass_Check(po_rrclass)) {
+                return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+            }
+            if (!PyRRType_Check(po_rrtype)) {
+                return (setTypeError(po_rrtype, "rrtype", "RRType"));
+            }
+            ConstRRsetPtr found_rrset = self->cppobj->find(
+                PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+                PyRRType_ToRRType(po_rrtype));
+            if (found_rrset) {
+                return (createRRsetObject(*found_rrset));
+            }
+            Py_RETURN_NONE;
+        }
+    } catch (const std::exception& ex) {
+        const string ex_what = "Unexpected failure in "
+            "RRsetCollectionBase.find: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef RRsetCollectionBase_methods[] = {
+    { "find", RRsetCollectionBase_find, METH_VARARGS,
+      RRsetCollectionBase_find_doc },
+    { NULL, NULL, 0, NULL }
+};
+} // 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_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_base_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "dns.RRsetCollectionBase",
+    sizeof(s_RRsetCollection),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    RRsetCollectionBase_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
+    NULL,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, // tp_flags (it's inheritable)
+    RRsetCollectionBase_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                 // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    RRsetCollectionBase_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
+    RRsetCollectionBase_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_RRsetCollectionBase(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(&rrset_collection_base_type) < 0) {
+        return (false);
+    }
+    void* p = &rrset_collection_base_type;
+    if (PyModule_AddObject(mod, "RRsetCollectionBase",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&rrset_collection_base_type);
+
+    return (true);
+}
+
+//
+// RRsetCollection
+//
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_RRsetCollection, RRsetCollection> RRsetCollectionContainer;
+
+int
+RRsetCollection_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    try {
+        const char* filename;
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        Py_buffer py_buf;
+
+        if (PyArg_ParseTuple(args, "sO!O!", &filename, &name_type, &po_name,
+                             &rrclass_type, &po_rrclass)) {
+            self->cppobj =
+                new RRsetCollection(filename, PyName_ToName(po_name),
+                                    PyRRClass_ToRRClass(po_rrclass));
+            return (0);
+        } else if (PyArg_ParseTuple(args, "y*O!O!", &py_buf, &name_type,
+                                    &po_name,&rrclass_type, &po_rrclass)) {
+            PyErr_Clear();      // clear the error for the first ParseTuple
+            const char* const cp = static_cast<const char*>(py_buf.buf);
+            std::istringstream iss(string(cp, cp + py_buf.len));
+            self->cppobj =
+                new RRsetCollection(iss, PyName_ToName(po_name),
+                                    PyRRClass_ToRRClass(po_rrclass));
+            return (0);
+        } else if (PyArg_ParseTuple(args, "")) {
+            PyErr_Clear();      // clear the error for the second ParseTuple
+            self->cppobj = new RRsetCollection;
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct RRsetCollection object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (-1);
+    }
+
+    // Default error string isn't helpful when it takes multiple combinations
+    // of args.  We provide our own.
+    PyErr_SetString(PyExc_TypeError, "Invalid argument(s) to RRsetCollection "
+                    "constructor; see pydoc");
+
+    return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollection_addRRset(PyObject* po_self, PyObject* args) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    try {
+        PyObject* po_rrset;
+        if (PyArg_ParseTuple(args, "O", &po_rrset)) {
+            if (!PyRRset_Check(po_rrset)) {
+                return (setTypeError(po_rrset, "rrset", "RRset"));
+            }
+            static_cast<RRsetCollection*>(self->cppobj)->addRRset(
+                PyRRset_ToRRsetPtr(po_rrset));
+            Py_RETURN_NONE;
+        }
+    } catch (const InvalidParameter& ex) { // duplicate add
+        PyErr_SetString(PyExc_ValueError, ex.what());
+        return (NULL);
+    } catch (const std::exception& ex) {
+        const string ex_what = "Unexpected failure in "
+            "RRsetCollection.add_rrset: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    return (NULL);
+}
+
+PyObject*
+RRsetCollection_removeRRset(PyObject* po_self, PyObject* args) {
+    s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+    try {
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        PyObject* po_rrtype;
+
+        if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+            if (!PyName_Check(po_name)) {
+                return (setTypeError(po_name, "name", "Name"));
+            }
+            if (!PyRRClass_Check(po_rrclass)) {
+                return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+            }
+            if (!PyRRType_Check(po_rrtype)) {
+                return (setTypeError(po_rrtype, "rrtype", "RRType"));
+            }
+            const bool result =
+                static_cast<RRsetCollection*>(self->cppobj)->removeRRset(
+                    PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+                    PyRRType_ToRRType(po_rrtype));
+            if (result) {
+                Py_RETURN_TRUE;
+            } else {
+                Py_RETURN_FALSE;
+            }
+        }
+    } catch (...) {}
+
+    return (NULL);
+}
+
+// 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 RRsetCollection_methods[] = {
+    { "add_rrset", RRsetCollection_addRRset, METH_VARARGS,
+      RRsetCollection_addRRset_doc },
+    { "remove_rrset", RRsetCollection_removeRRset, METH_VARARGS,
+      RRsetCollection_removeRRset_doc },
+    { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "dns.RRsetCollection",
+    sizeof(s_RRsetCollection),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    RRsetCollection_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
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    RRsetCollection_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    RRsetCollection_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    &rrset_collection_base_type,        // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    RRsetCollection_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_RRsetCollection(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(&rrset_collection_type) < 0) {
+        return (false);
+    }
+    void* p = &rrset_collection_type;
+    if (PyModule_AddObject(mod, "RRsetCollection",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&rrset_collection_type);
+
+    return (true);
+}
+
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/rrset_collection_python.h b/src/lib/dns/python/rrset_collection_python.h
new file mode 100644
index 0000000..98cb84b
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2013  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_RRSETCOLLECTION_H
+#define PYTHON_RRSETCOLLECTION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRsetCollectionBase;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+// This structure will be commonly used for all derived classes of
+// RRsetCollectionBase.  cppobj will point to an instance of the specific
+// derived class.
+class s_RRsetCollection : public PyObject {
+public:
+    s_RRsetCollection() : cppobj(NULL) {}
+    RRsetCollectionBase* cppobj;
+};
+
+// Python type information for RRsetCollectionBase
+extern PyTypeObject rrset_collection_base_type;
+
+// Python type information for dns.RRsetCollection
+extern PyTypeObject rrset_collection_type;
+
+bool initModulePart_RRsetCollectionBase(PyObject* mod);
+bool initModulePart_RRsetCollection(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_RRSETCOLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_collection_python_inc.cc b/src/lib/dns/python/rrset_collection_python_inc.cc
new file mode 100644
index 0000000..5c1e532
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python_inc.cc
@@ -0,0 +1,148 @@
+namespace {
+// Modifications
+//   - libdns++ => isc.dns, libdatasrc => isc.datasrc
+//   - note about the constructor.
+//   - add note about iteration
+const char* const RRsetCollectionBase_doc = "\
+Generic class to represent a set of RRsets.\n\
+\n\
+This is a generic container and the stored set of RRsets does not\n\
+necessarily form a valid zone (e.g. there doesn't necessarily have to\n\
+be an SOA at the \"origin\"). Instead, it will be used to represent a\n\
+single zone for the purpose of zone loading/checking. It provides a\n\
+simple find() method to find an RRset for the given name and type (and\n\
+maybe class) and a way to iterate over all RRsets.\n\
+\n\
+    Note: in the initial version, iteration is not yet supported.\n\
+\n\
+See RRsetCollection for a simple isc.dns implementation. Other modules\n\
+such as isc.datasrc will have another implementation.\n\
+\n\
+This base class cannot be directly instantiated, so no constructor is\n\
+defined.\n\
+\n\
+";
+
+// Modifications
+//   - ConstRRset => RRset
+//   - NULL => None
+//   - added types of params
+const char* const RRsetCollectionBase_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, None is\n\
+returned.\n\
+\n\
+Parameters:\n\
+  name       (isc.dns.Name) The name of the RRset to search for.\n\
+  rrtype     (isc.dns.RRType) The type of the RRset to search for.\n\
+  rrclass    (isc.dns.RRClass) The class of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, None otherwise.\n\
+";
+
+
+// Modifications
+//   - libdns++ => isc.dns
+//   - remove STL
+//   - MasterLoaderError => IscException
+//   - added types of params
+//  - input_stream => input, stream => bytes
+const char* const RRsetCollection_doc = "\
+Derived class implementation of RRsetCollectionBase for isc.dns module.\n\
+\n\
+RRsetCollection()\n\
+\n\
+    Constructor.\n\
+\n\
+    This constructor creates an empty collection without any data in\n\
+    it. RRsets can be added to the collection with the add_rrset()\n\
+    method.\n\
+\n\
+RRsetCollection(filename, origin, rrclass)\n\
+\n\
+    Constructor.\n\
+\n\
+    The origin and rrclass arguments are required for the zone\n\
+    loading, but RRsetCollection itself does not do any validation,\n\
+    and the collection of RRsets does not have to form a valid zone.\n\
+    The constructor throws IscException if there is an error\n\
+    during loading.\n\
+\n\
+    Parameters:\n\
+      filename   (str) Name of a file containing a collection of RRs in the\n\
+                 master file format (which may or may not form a valid\n\
+                 zone).\n\
+      origin     (isc.dns.Name) The zone origin.\n\
+      rrclass    (isc.dns.RRClass) The zone class.\n\
+\n\
+RRsetCollection(input_stream, origin, rrclass)\n\
+\n\
+    Constructor.\n\
+\n\
+    This constructor is similar to the previous one, but instead of\n\
+    taking a filename to load a zone from, it takes a byte object,\n\
+    representing the zone contents in text.\n\
+    The constructor throws IscException if there is an error\n\
+    during loading.\n\
+\n\
+    Parameters:\n\
+      input      (byte) Textual representation of the zone.\n\
+      origin     (isc.dns.Name) The zone origin.\n\
+      rrclass    (isc.dns.RRClass) The zone class.\n\
+\n\
+";
+
+// Modifications
+//   - void => None
+//   - InvalidParameter => ValueError
+//   - remove ownership related points (doesn't apply here)
+const char* const RRsetCollection_addRRset_doc = "\
+add_rrset(rrset) -> None\n\
+\n\
+Add an RRset to the collection.\n\
+\n\
+Does not do any validation whether rrset belongs to a particular zone\n\
+or not.\n\
+\n\
+It throws a ValueError exception if an rrset with the same\n\
+class, type and name already exists.\n\
+\n\
+";
+
+// Modifications
+//   - ConstRRset => RRset
+const char* const RRsetCollection_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, NULL is\n\
+returned.\n\
+\n\
+Parameters:\n\
+  name       The name of the RRset to search for.\n\
+  rrclass    The class of the RRset to search for.\n\
+  rrtype     The type of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, NULL otherwise.\n\
+";
+
+// Modifications
+//   //   - true/false => True/False
+const char* const RRsetCollection_removeRRset_doc = "\
+remove_rrset(name, rrclass, rrtype) -> bool\n\
+\n\
+Remove an RRset from the collection.\n\
+\n\
+RRset(s) matching the name, rrclass and rrtype are removed from the\n\
+collection.\n\
+\n\
+True if a matching RRset was deleted, False if no such RRset exists.\n\
+\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 4b0ea9f..de6b010 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -12,12 +12,14 @@ PYTESTS += rrclass_python_test.py
 PYTESTS += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_python_test.py
+PYTESTS += rrset_collection_python_test.py
 PYTESTS += serial_python_test.py
 PYTESTS += tsig_python_test.py
 PYTESTS += tsig_rdata_python_test.py
 PYTESTS += tsigerror_python_test.py
 PYTESTS += tsigkey_python_test.py
 PYTESTS += tsigrecord_python_test.py
+PYTESTS += zone_checker_python_test.py
 
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += testutil.py
diff --git a/src/lib/dns/python/tests/rrset_collection_python_test.py b/src/lib/dns/python/tests/rrset_collection_python_test.py
new file mode 100644
index 0000000..2cf286e
--- /dev/null
+++ b/src/lib/dns/python/tests/rrset_collection_python_test.py
@@ -0,0 +1,140 @@
+# Copyright (C) 2013  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 os
+import unittest
+from pydnspp import *
+
+# This should refer to the testdata diretory for the C++ tests.
+TESTDATA_DIR = os.environ["TESTDATA_PATH"].split(':')[0]
+
+class RRsetCollectionBaseTest(unittest.TestCase):
+    def test_init(self):
+        # direct instantiation of the base class is prohibited.
+        self.assertRaises(TypeError, RRsetCollectionBase)
+
+class RRsetCollectionTest(unittest.TestCase):
+    def test_init_fail(self):
+        # check various failure cases on construction (other normal cases are
+        # covered as part of other tests)
+
+        # bad args
+        self.assertRaises(TypeError, RRsetCollection, 1)
+        self.assertRaises(TypeError, RRsetCollection, # extra arg
+                          b'example. 0 A 192.0.2.1',
+                          Name('example'), RRClass.IN(), 1)
+        self.assertRaises(TypeError, RRsetCollection, # incorrect order
+                          b'example. 0 A 192.0.2.1', RRClass.IN(),
+                          Name('example'))
+
+        # constructor will result in C++ exception.
+        self.assertRaises(IscException, RRsetCollection,
+                          TESTDATA_DIR + '/no_such_file', Name('example.org'),
+                          RRClass.IN())
+
+    def check_find_result(self, rrsets):
+        # Commonly used check pattern
+        found = rrsets.find(Name('www.example.org'), RRClass.IN(), RRType.A())
+        self.assertNotEqual(None, found)
+        self.assertEqual(Name('www.example.org'), found.get_name())
+        self.assertEqual(RRClass.IN(), found.get_class())
+        self.assertEqual(RRType.A(), found.get_type())
+        self.assertEqual('192.0.2.1', found.get_rdata()[0].to_text())
+
+    def test_find(self):
+        # Checking the underlying find() is called as intended, both for
+        # success and failure cases, and with two different constructors.
+        rrsets = RRsetCollection(TESTDATA_DIR + '/example.org',
+                                 Name('example.org'), RRClass.IN())
+        self.check_find_result(rrsets)
+        self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+                                           RRType.A()))
+
+        rrsets = RRsetCollection(b'www.example.org. 3600 IN A 192.0.2.1',
+                                 Name('example.org'), RRClass.IN())
+        self.check_find_result(rrsets)
+        self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+                                           RRType.A()))
+
+    def test_find_badargs(self):
+        rrsets = RRsetCollection()
+
+        # Check bad arguments: bad types
+        self.assertRaises(TypeError, rrsets.find, 1, RRClass.IN(), RRType.A())
+        self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+                          RRType.A())
+        self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+                          RRType.A())
+        self.assertRaises(TypeError, rrsets.find, Name('example'),
+                          RRClass.IN(), 1)
+        self.assertRaises(TypeError, rrsets.find, Name('example'), RRType.A(),
+                          RRClass.IN())
+
+        # Check bad arguments: too many/few arguments
+        self.assertRaises(TypeError, rrsets.find, Name('example'),
+                          RRClass.IN(), RRType.A(), 0)
+        self.assertRaises(TypeError, rrsets.find, Name('example'),
+                          RRClass.IN())
+
+    def test_add_remove_rrset(self):
+        name = Name('www.example.org')
+        rrclass = RRClass.IN()
+        rrtype = RRType.A()
+
+        # Create a collection with no RRsets
+        rrsets = RRsetCollection()
+        self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+        # add the record, then it should be found
+        rrset = RRset(name, rrclass, rrtype, RRTTL(60))
+        rrset.add_rdata(Rdata(rrtype, rrclass, '192.0.2.1'))
+        self.assertEqual(None, rrsets.add_rrset(rrset))
+        self.check_find_result(rrsets)
+
+        # duplicate add is (at least currently) rejected
+        self.assertRaises(ValueError, rrsets.add_rrset, rrset)
+
+        # remove it, then we cannot find it any more.
+        self.assertTrue(rrsets.remove_rrset(name, rrclass, rrtype))
+        self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+        # duplicate remove (specified RRset doesn't exist) reulsts in False
+        self.assertFalse(rrsets.remove_rrset(name, rrclass, rrtype))
+
+        # Checking bad args
+        self.assertRaises(TypeError, rrsets.add_rrset, 1)
+        self.assertRaises(TypeError, rrsets.add_rrset, rrset, 1)
+        self.assertRaises(TypeError, rrsets.add_rrset)
+
+        self.assertRaises(TypeError, rrsets.remove_rrset, 1, rrclass, rrtype)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, 1, rrtype)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass, 1)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrtype,
+                          rrclass)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass)
+        self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass,
+                          rrtype, 1)
+
+    def test_empty_class(self):
+        # A user defined collection class shouldn't cause disruption.
+        class EmptyRRsetCollection(RRsetCollectionBase):
+            def __init__(self):
+                pass
+        rrsets = EmptyRRsetCollection()
+        self.assertRaises(TypeError, rrsets.find, Name('www.example.org'),
+                          RRClass.IN(), RRType.A())
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/src/lib/dns/python/tests/zone_checker_python_test.py b/src/lib/dns/python/tests/zone_checker_python_test.py
new file mode 100644
index 0000000..66b6c47
--- /dev/null
+++ b/src/lib/dns/python/tests/zone_checker_python_test.py
@@ -0,0 +1,179 @@
+# Copyright (C) 2013  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 *
+
+# A separate exception class raised from some tests to see if it's propagated.
+class FakeException(Exception):
+    pass
+
+class ZoneCheckerTest(unittest.TestCase):
+    def __callback(self, reason, reasons):
+        # Issue callback for check_zone().  It simply records the given reason
+        # string in the given list.
+        reasons.append(reason)
+
+    def test_check(self):
+        errors = []
+        warns = []
+
+        # A successful case with no warning.
+        rrsets = RRsetCollection(b'example.org. 0 SOA . . 0 0 0 0 0\n' +
+                                 b'example.org. 0 NS ns.example.org.\n' +
+                                 b'ns.example.org. 0 A 192.0.2.1\n',
+                                 Name('example.org'), RRClass.IN())
+        self.assertTrue(check_zone(Name('example.org'), RRClass.IN(),
+                                   rrsets,
+                                   (lambda r: self.__callback(r, errors),
+                                    lambda r: self.__callback(r, warns))))
+        self.assertEqual([], errors)
+        self.assertEqual([], warns)
+
+        # Check fails and one additional warning.
+        rrsets = RRsetCollection(b'example.org. 0 NS ns.example.org.',
+                                 Name('example.org'), RRClass.IN())
+        self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+                                    (lambda r: self.__callback(r, errors),
+                                     lambda r: self.__callback(r, warns))))
+        self.assertEqual(['zone example.org/IN: has 0 SOA records'], errors)
+        self.assertEqual(['zone example.org/IN: NS has no address records ' +
+                          '(A or AAAA)'], warns)
+
+        # Same RRset collection, suppressing callbacks
+        errors = []
+        warns = []
+        self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+                                    (None, None)))
+        self.assertEqual([], errors)
+        self.assertEqual([], warns)
+
+    def test_check_badarg(self):
+        rrsets = RRsetCollection()
+        # Bad types
+        self.assertRaises(TypeError, check_zone, 1, RRClass.IN(), rrsets,
+                          (None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), 1, rrsets,
+                          (None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          1, (None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, 1)
+
+        # Bad callbacks
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, None, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (1, None))
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, 1))
+
+        # Extra/missing args
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, None), 1)
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets)
+        check_zone(Name('example'), RRClass.IN(), rrsets, (None, None))
+
+    def test_check_callback_fail(self):
+        # Let the call raise a Python exception.  It should be propagated to
+        # the top level.
+        def __bad_callback(reason):
+            raise FakeException('error in callback')
+
+        # Using an empty collection, triggering an error callback.
+        self.assertRaises(FakeException, check_zone, Name('example.org'),
+                          RRClass.IN(), RRsetCollection(),
+                          (__bad_callback, None))
+
+        # An unusual case: the callback is expected to return None, but if it
+        # returns an actual object it shouldn't cause leak inside the callback.
+        class RefChecker:
+            pass
+        def __callback(reason, checker):
+            return checker
+
+        ref_checker = RefChecker()
+        orig_refcnt = sys.getrefcount(ref_checker)
+        check_zone(Name('example.org'), RRClass.IN(), RRsetCollection(),
+                   (lambda r: __callback(r, ref_checker), None))
+        self.assertEqual(orig_refcnt, sys.getrefcount(ref_checker))
+
+    def test_check_custom_collection(self):
+        # Test if check_zone() works with pure-Python RRsetCollection.
+
+        class FakeRRsetCollection(RRsetCollectionBase):
+            # This is the Python-only collection class.  Its find() makes
+            # the check pass by default, by returning hardcoded RRsets.
+            # If raise_on_find is set to True, find() raises an exception.
+            # If find_result is set to something other than 'use_default'
+            # (as a string), find() returns that specified value (note that
+            # it can be None).
+
+            def __init__(self, raise_on_find=False, find_result='use_default'):
+                self.__raise_on_find = raise_on_find
+                self.__find_result = find_result
+
+            def find(self, name, rrclass, rrtype):
+                if self.__raise_on_find:
+                    raise FakeException('find error')
+                if self.__find_result is not 'use_default':
+                    return self.__find_result
+                if rrtype == RRType.SOA():
+                    soa = RRset(Name('example'), RRClass.IN(), rrtype,
+                                RRTTL(0))
+                    soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
+                                        '. . 0 0 0 0 0'))
+                    return soa
+                if rrtype == RRType.NS():
+                    ns = RRset(Name('example'), RRClass.IN(), rrtype,
+                               RRTTL(0))
+                    ns.add_rdata(Rdata(RRType.NS(), RRClass.IN(),
+                                       'example.org'))
+                    return ns
+                return None
+
+        # A successful case.  Just checking it works in that case.
+        rrsets = FakeRRsetCollection()
+        self.assertTrue(check_zone(Name('example'), RRClass.IN(), rrsets,
+                                   (None, None)))
+
+        # Likewise, normal case but zone check fails.
+        rrsets = FakeRRsetCollection(False, None)
+        self.assertFalse(check_zone(Name('example'), RRClass.IN(), rrsets,
+                                    (None, None)))
+
+        # Our find() returns a bad type of result.
+        rrsets = FakeRRsetCollection(False, 1)
+        self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+                          rrsets, (None, None))
+
+        # Our find() returns an empty SOA RRset.  C++ zone checker code
+        # throws, which results in IscException.
+        rrsets = FakeRRsetCollection(False, RRset(Name('example'),
+                                                  RRClass.IN(),
+                                                  RRType.SOA(), RRTTL(0)))
+        self.assertRaises(IscException, check_zone, Name('example'),
+                          RRClass.IN(), rrsets, (None, None))
+
+        # Our find() raises an exception.  That exception is propagated to
+        # the top level.
+        rrsets = FakeRRsetCollection(True)
+        self.assertRaises(FakeException, check_zone, Name('example'),
+                          RRClass.IN(), rrsets, (None, None))
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/src/lib/dns/python/zone_checker_python.cc b/src/lib/dns/python/zone_checker_python.cc
new file mode 100644
index 0000000..e78be95
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.cc
@@ -0,0 +1,224 @@
+// Copyright (C) 2013  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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/zone_checker_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+#include <dns/zone_checker.h>
+
+#include <boost/bind.hpp>
+
+#include <cstring>
+#include <string>
+#include <stdexcept>
+
+using std::string;
+using isc::util::python::PyObjectContainer;
+using namespace isc::dns;
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+    PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+                 var_name, type_name, pobj->ob_type->tp_name);
+    return (NULL);
+}
+}
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+namespace {
+// This is used to abort check_zone() and go back to the top level.
+// We use a separate exception so it won't be caught in the middle.
+class InternalException : public std::exception {
+};
+
+// This is a "wrapper" RRsetCollection subclass.  It's constructed with
+// a Python RRsetCollection object, and its find() calls the Python version
+// of RRsetCollection.find().  This way, the check_zone() wrapper will work
+// for pure-Python RRsetCollection classes, too.
+class PyRRsetCollection : public RRsetCollectionBase {
+public:
+    PyRRsetCollection(PyObject* po_rrsets) : po_rrsets_(po_rrsets) {}
+
+    virtual ConstRRsetPtr find(const Name& name, const RRClass& rrclass,
+                               const RRType& rrtype) const {
+        try {
+            // Convert C++ args to Python objects, and builds argument tuple
+            // to the Python method.  This should basically succeed.
+            PyObjectContainer poc_name(createNameObject(name));
+            PyObjectContainer poc_rrclass(createRRClassObject(rrclass));
+            PyObjectContainer poc_rrtype(createRRTypeObject(rrtype));
+            PyObjectContainer poc_args(Py_BuildValue("(OOOO)",
+                                                     po_rrsets_,
+                                                     poc_name.get(),
+                                                     poc_rrclass.get(),
+                                                     poc_rrtype.get()));
+
+            // Call the Python method.
+            // PyObject_CallMethod is dirty and requires mutable C-string for
+            // method name and arguments.  While it's unlikely for these to
+            // be modified, we err on the side of caution and make copies.
+            char method_name[sizeof("find")];
+            char method_args[sizeof("(OOO)")];
+            std::strcpy(method_name, "find");
+            std::strcpy(method_args, "(OOO)");
+            PyObjectContainer poc_result(
+                PyObject_CallMethod(po_rrsets_, method_name, method_args,
+                                    poc_name.get(), poc_rrclass.get(),
+                                    poc_rrtype.get()));
+            PyObject* const po_result = poc_result.get();
+            if (po_result == Py_None) {
+                return (ConstRRsetPtr());
+            } else if (PyRRset_Check(po_result)) {
+                return (PyRRset_ToRRsetPtr(po_result));
+            } else {
+                PyErr_SetString(PyExc_TypeError, "invalid type for "
+                                "RRsetCollection.find(): must be None "
+                                "or RRset");
+                throw InternalException();
+            }
+        } catch (const isc::util::python::PyCPPWrapperException& ex) {
+            // This normally means the method call fails.  Propagate the
+            // already-set Python error to the top level.  Other C++ exceptions
+            // are really unexpected, so we also (implicitly) propagate it
+            // to the top level and recognize it as "unexpected failure".
+            throw InternalException();
+        }
+    }
+
+    virtual IterPtr getBeginning() {
+        isc_throw(NotImplemented, "iterator support is not yet available");
+    }
+    virtual IterPtr getEnd() {
+        isc_throw(NotImplemented, "iterator support is not yet available");
+    }
+
+private:
+    PyObject* const po_rrsets_;
+};
+
+void
+callback(const string& reason, PyObject* obj) {
+    PyObjectContainer poc_args(Py_BuildValue("(s#)", reason.c_str(),
+                                             reason.size()));
+    PyObject* po_result = PyObject_CallObject(obj, poc_args.get());
+    if (po_result == NULL) {
+        throw InternalException();
+    }
+    Py_DECREF(po_result);
+}
+
+ZoneCheckerCallbacks::IssueCallback
+PyCallable_ToCallback(PyObject* obj) {
+    if (obj == Py_None) {
+        return (NULL);
+    }
+    return (boost::bind(callback, _1, obj));
+}
+
+}
+
+PyObject*
+pyCheckZone(PyObject*, PyObject* args) {
+    try {
+        PyObject* po_name;
+        PyObject* po_rrclass;
+        PyObject* po_rrsets;
+        PyObject* po_error;
+        PyObject* po_warn;
+
+        if (PyArg_ParseTuple(args, "OOO(OO)", &po_name, &po_rrclass,
+                             &po_rrsets, &po_error, &po_warn)) {
+            if (!PyName_Check(po_name)) {
+                return (setTypeError(po_name, "zone_name", "Name"));
+            }
+            if (!PyRRClass_Check(po_rrclass)) {
+                return (setTypeError(po_rrclass, "zone_rrclass", "RRClass"));
+            }
+            if (!PyObject_TypeCheck(po_rrsets, &rrset_collection_base_type)) {
+                return (setTypeError(po_rrsets, "zone_rrsets",
+                                     "RRsetCollectionBase"));
+            }
+            if (po_error != Py_None && PyCallable_Check(po_error) == 0) {
+                return (setTypeError(po_error, "error", "callable or None"));
+            }
+            if (po_warn != Py_None && PyCallable_Check(po_warn) == 0) {
+                return (setTypeError(po_warn, "warn", "callable or None"));
+            }
+
+            PyRRsetCollection py_rrsets(po_rrsets);
+            if (checkZone(PyName_ToName(po_name),
+                          PyRRClass_ToRRClass(po_rrclass), py_rrsets,
+                          ZoneCheckerCallbacks(
+                              PyCallable_ToCallback(po_error),
+                              PyCallable_ToCallback(po_warn)))) {
+                Py_RETURN_TRUE;
+            } else {
+                Py_RETURN_FALSE;
+            }
+        }
+    } catch (const InternalException& ex) {
+        // Normally, error string should have been set already.  For some
+        // rare cases such as memory allocation failure, we set the last-resort
+        // error string.
+        if (PyErr_Occurred() == NULL) {
+            PyErr_SetString(PyExc_SystemError,
+                            "Unexpected failure in check_zone()");
+        }
+        return (NULL);
+    } catch (const std::exception& ex) {
+        const string ex_what = "Unexpected failure in check_zone(): " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    return (NULL);
+}
+
+} // namespace internal
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/zone_checker_python.h b/src/lib/dns/python/zone_checker_python.h
new file mode 100644
index 0000000..63168fd
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2013  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_ZONE_CHECKER_H
+#define PYTHON_ZONE_CHECKER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+PyObject* pyCheckZone(PyObject* self, PyObject* args);
+
+} // namespace python
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/zone_checker_python_inc.cc b/src/lib/dns/python/zone_checker_python_inc.cc
new file mode 100644
index 0000000..c99042e
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python_inc.cc
@@ -0,0 +1,79 @@
+namespace {
+// Modifications
+//   - callbacks => (error, warn)
+//   - recover paragraph before itemization (it's a bug of convert script)
+//   - correct broken format for nested items (another bug of script)
+//   - true/false => True/False
+//   - removed Exception section (for simplicity)
+const char* const dns_checkZone_doc = "\
+check_zone(zone_name, zone_class, zone_rrsets, (error, warn)) -> bool\n\
+\n\
+Perform basic integrity checks on zone RRsets.\n\
+\n\
+This function performs some lightweight checks on zone's SOA and\n\
+(apex) NS records. Here, lightweight means it doesn't require\n\
+traversing the entire zone, and should be expected to complete\n\
+reasonably quickly regardless of the size of the zone.\n\
+\n\
+It distinguishes \"critical\" errors and other undesirable issues: the\n\
+former should be interpreted as the resulting zone shouldn't be used\n\
+further, e.g, by an authoritative server implementation; the latter\n\
+means the issues are better to be addressed but are not necessarily\n\
+considered to make the zone invalid. Critical errors are reported via\n\
+the error() function, and non critical issues are reported via warn().\n\
+\n\
+Specific checks performed by this function is as follows.  Failure of\n\
+a check is considered a critical error unless noted otherwise:\n\
+\n\
+- There is exactly one SOA RR at the zone apex.\n\
+- There is at least one NS RR at the zone apex.\n\
+- For each apex NS record, if the NS name (the RDATA of the record) is\n\
+  in the zone (i.e., it's a subdomain of the zone origin and above any\n\
+  zone cut due to delegation), check the following:\n\
+  - the NS name should have an address record (AAAA or A). Failure of\n\
+    this check is considered a non critical issue.\n\
+  - the NS name does not have a CNAME. This is prohibited by Section\n\
+    10.3 of RFC 2181.\n\
+  - the NS name is not subject to DNAME substitution. This is prohibited\n\
+    by Section 4 of RFC 6672.\n\
+\n\
+In addition, when the check is completed without any\n\
+critical error, this function guarantees that RRsets for the SOA and\n\
+(apex) NS stored in the passed RRset collection have the expected\n\
+type of Rdata objects, i.e., generic.SOA and generic.NS,\n\
+respectively. (This is normally expected to be the case, but not\n\
+guaranteed by the API).\n\
+\n\
+As for the check on the existence of AAAA or A records for NS names,\n\
+it should be noted that BIND 9 treats this as a critical error. It's\n\
+not clear whether it's an implementation dependent behavior or based\n\
+on the protocol standard (it looks like the former), but to make it\n\
+sure we need to confirm there is even no wildcard match for the names.\n\
+This should be a very rare configuration, and more expensive to\n\
+detect, so we do not check this condition, and treat this case as a\n\
+non critical issue.\n\
+\n\
+This function indicates the result of the checks (whether there is a\n\
+critical error) via the return value: It returns True if there is no\n\
+critical error and returns False otherwise. It doesn't throw an\n\
+exception on encountering an error so that it can report as many\n\
+errors as possible in a single call. If an exception is a better way\n\
+to signal the error, the caller can pass a callable object as error()\n\
+that throws.\n\
+\n\
+This function can still throw an exception if it finds a really bogus\n\
+condition that is most likely to be an implementation bug of the\n\
+caller. Such cases include when an RRset contained in the RRset\n\
+collection is empty.\n\
+\n\
+Parameters:\n\
+  zone_name  The name of the zone to be checked\n\
+  zone_class The RR class of the zone to be checked\n\
+  zone_rrsets The collection of RRsets of the zone\n\
+  error      Callable object used to report errors\n\
+  warn       Callable object used to report non-critical issues\n\
+\n\
+Return Value(s): True if no critical errors are found; False\n\
+otherwise.\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index 4cd63cc..3fe0c74 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -60,7 +60,7 @@ public:
 
 ///
 /// \brief A standard DNS module exception that is thrown if RDATA parser
-/// parser encounters a character-string (as defined in RFC1035) exceeding
+/// encounters a character-string (as defined in RFC1035) exceeding
 /// the maximum allowable length (\c MAX_CHARSTRING_LEN).
 ///
 class CharStringTooLong : public Exception {
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
index fb4c9b4..e2857b3 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.cc
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -57,8 +57,8 @@ decimalToNumber(const char* s, const char* s_end) {
 }
 
 void
-strToCharString(const MasterToken::StringRegion& str_region,
-                CharString& result)
+stringToCharString(const MasterToken::StringRegion& str_region,
+                   CharString& result)
 {
     // make a space for the 1-byte length field; filled in at the end
     result.push_back(0);
@@ -91,6 +91,29 @@ strToCharString(const MasterToken::StringRegion& str_region,
     result[0] = result.size() - 1;
 }
 
+std::string
+charStringToString(const CharString& char_string) {
+    std::string s;
+    for (CharString::const_iterator it = char_string.begin() + 1;
+         it != char_string.end(); ++it) {
+        const uint8_t ch = *it;
+        if ((ch < 0x20) || (ch >= 0x7f)) {
+            // convert to escaped \xxx (decimal) format
+            s.push_back('\\');
+            s.push_back('0' + ((ch / 100) % 10));
+            s.push_back('0' + ((ch / 10) % 10));
+            s.push_back('0' + (ch % 10));
+            continue;
+        }
+        if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+            s.push_back('\\');
+        }
+        s.push_back(ch);
+    }
+
+    return (s);
+}
+
 } // end of detail
 } // end of generic
 } // end of rdata
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
index 702af04..4a146db 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.h
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -17,6 +17,7 @@
 
 #include <dns/master_lexer.h>
 
+#include <string>
 #include <vector>
 #include <stdint.h>
 
@@ -48,8 +49,22 @@ typedef std::vector<uint8_t> CharString;
 /// \brief str_region A string that represents a character-string.
 /// \brief result A placeholder vector where the resulting data are to be
 /// stored.  Expected to be empty, but it's not checked.
-void strToCharString(const MasterToken::StringRegion& str_region,
-                     CharString& result);
+void stringToCharString(const MasterToken::StringRegion& str_region,
+                        CharString& result);
+
+/// \brief Convert a CharString into a textual DNS character-string.
+///
+/// This method converts a binary 8-bit representation of a DNS
+/// character string into a textual string representation, escaping any
+/// special characters in the process. For example, characters like
+/// double-quotes, semi-colon and backspace are prefixed with backspace
+/// character, and characters not in the printable range of [0x20, 0x7e]
+/// (inclusive) are converted to the \xxx 3-digit decimal
+/// representation.
+///
+/// \param char_string The \c CharString to convert.
+/// \return A string representation of \c char_string.
+std::string charStringToString(const CharString& char_string);
 
 } // namespace detail
 } // namespace generic
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
index d1916e3..b48d109 100644
--- a/src/lib/dns/rdata/generic/detail/txt_like.h
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -119,7 +119,7 @@ private:
                 break;
             }
             string_list_.push_back(std::vector<uint8_t>());
-            strToCharString(token.getStringRegion(), string_list_.back());
+            stringToCharString(token.getStringRegion(), string_list_.back());
         }
 
         // Let upper layer handle eol/eof.
@@ -177,18 +177,14 @@ public:
     toText() const {
         std::string s;
 
-        // XXX: this implementation is not entirely correct.  for example, it
-        // should escape double-quotes if they appear in the character string.
         for (std::vector<std::vector<uint8_t> >::const_iterator it =
-                 string_list_.begin();
-             it != string_list_.end();
-             ++it)
+                 string_list_.begin(); it != string_list_.end(); ++it)
         {
             if (!s.empty()) {
                 s.push_back(' ');
             }
             s.push_back('"');
-            s.insert(s.end(), (*it).begin() + 1, (*it).end());
+            s.append(charStringToString(*it));
             s.push_back('"');
         }
 
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
index 70d6b78..1ff4163 100644
--- a/src/lib/dns/rrclass-placeholder.h
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -22,6 +22,8 @@
 
 #include <exceptions/exceptions.h>
 
+#include <boost/optional.hpp>
+
 namespace isc {
 namespace util {
 class InputBuffer;
@@ -33,6 +35,16 @@ namespace dns {
 // forward declarations
 class AbstractMessageRenderer;
 
+class RRClass; // forward declaration to define MaybeRRClass.
+
+/// \brief A shortcut for a compound type to represent RRClass-or-not.
+///
+/// A value of this type can be interpreted in a boolean context, whose
+/// value is \c true if and only if it contains a valid RRClass object.
+/// And, if it contains a valid RRClass object, its value is accessible
+/// using \c operator*, just like a bare pointer to \c RRClass.
+typedef boost::optional<RRClass> MaybeRRClass;
+
 ///
 /// \brief A standard DNS module exception that is thrown if an RRClass object
 /// is being constructed from an unrecognized string.
@@ -123,8 +135,8 @@ public:
     /// If the given string is not recognized as a valid representation of
     /// an RR class, an exception of class \c InvalidRRClass will be thrown.
     ///
-    /// \param classstr A string representation of the \c RRClass
-    explicit RRClass(const std::string& classstr);
+    /// \param class_str A string representation of the \c RRClass
+    explicit RRClass(const std::string& class_str);
     /// Constructor from wire-format data.
     ///
     /// The \c buffer parameter normally stores a complete DNS message
@@ -136,6 +148,45 @@ public:
     ///
     /// \param buffer A buffer storing the wire format data.
     explicit RRClass(isc::util::InputBuffer& buffer);
+
+    /// A separate factory of RRClass from text.
+    ///
+    /// This static method is similar to the constructor that takes a
+    /// string object, but works as a factory and reports parsing
+    /// failure in the form of the return value.  Normally the
+    /// constructor version should suffice, but in some cases the caller
+    /// may have to expect mixture of valid and invalid input, and may
+    /// want to minimize the overhead of possible exception handling.
+    /// This version is provided for such purpose.
+    ///
+    /// For the format of the \c class_str argument, see the
+    /// <code>RRClass(const std::string&)</code> constructor.
+    ///
+    /// If the given text represents a valid RRClass, it returns a
+    /// \c MaybeRRClass object that stores a corresponding \c RRClass
+    /// object, which is accessible via \c operator*().  In this case
+    /// the returned object will be interpreted as \c true in a boolean
+    /// context.  If the given text does not represent a valid RRClass,
+    /// it returns a \c MaybeRRClass object which is interpreted as
+    /// \c false in a boolean context.
+    ///
+    /// One main purpose of this function is to minimize the overhead
+    /// when the given text does not represent a valid RR class.  For
+    /// this reason this function intentionally omits the capability of
+    /// delivering a detailed reason for the parse failure, such as in the
+    /// \c want() string when exception is thrown from the constructor
+    /// (it will internally require a creation of string object, which
+    /// is relatively expensive).  If such detailed information is
+    /// necessary, the constructor version should be used to catch the
+    /// resulting exception.
+    ///
+    /// This function never throws the \c InvalidRRClass exception.
+    ///
+    /// \param class_str A string representation of the \c RRClass.
+    /// \return A MaybeRRClass object either storing an RRClass object
+    /// for the given text or a \c false value.
+    static MaybeRRClass createFromText(const std::string& class_str);
+
     ///
     /// We use the default copy constructor intentionally.
     //@}
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
index ac5823c..76a2e73 100644
--- a/src/lib/dns/rrclass.cc
+++ b/src/lib/dns/rrclass.cc
@@ -30,8 +30,11 @@ using namespace isc::util;
 namespace isc {
 namespace dns {
 
-RRClass::RRClass(const std::string& classstr) {
-    classcode_ = RRParamRegistry::getRegistry().textToClassCode(classstr);
+RRClass::RRClass(const std::string& class_str) {
+    if (!RRParamRegistry::getRegistry().textToClassCode(class_str, classcode_)) {
+        isc_throw(InvalidRRClass,
+                  "Unrecognized RR class string: " + class_str);
+    }
 }
 
 RRClass::RRClass(InputBuffer& buffer) {
@@ -56,6 +59,16 @@ RRClass::toWire(AbstractMessageRenderer& renderer) const {
     renderer.writeUint16(classcode_);
 }
 
+MaybeRRClass
+RRClass::createFromText(const string& class_str) {
+    uint16_t class_code;
+    if (RRParamRegistry::getRegistry().textToClassCode(class_str,
+                                                       class_code)) {
+        return (MaybeRRClass(class_code));
+    }
+    return (MaybeRRClass());
+}
+
 ostream&
 operator<<(ostream& os, const RRClass& rrclass) {
     os << rrclass.toText();
diff --git a/src/lib/dns/rrcollator.cc b/src/lib/dns/rrcollator.cc
index 4b12222..153de04 100644
--- a/src/lib/dns/rrcollator.cc
+++ b/src/lib/dns/rrcollator.cc
@@ -42,7 +42,7 @@ public:
                const RdataPtr& rdata);
 
     RRsetPtr current_rrset_;
-    AddRRsetCallback callback_;
+    const AddRRsetCallback callback_;
 };
 
 namespace {
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index 16ec23c..5960759 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -421,14 +421,15 @@ removeParam(uint16_t code, MC& codemap, MS& stringmap) {
     return (false);
 }
 
-template <typename PT, typename MS, typename ET>
-inline uint16_t
-textToCode(const string& code_str, MS& stringmap) {
+template <typename PT, typename MS>
+inline bool
+textToCode(const string& code_str, MS& stringmap, uint16_t& ret_code) {
     typename MS::const_iterator found;
 
     found = stringmap.find(code_str);
     if (found != stringmap.end()) {
-        return (found->second->code_);
+        ret_code = found->second->code_;
+        return (true);
     }
 
     size_t l = code_str.size();
@@ -441,10 +442,12 @@ textToCode(const string& code_str, MS& stringmap) {
                                           l - PT::UNKNOWN_PREFIXLEN()));
         iss >> dec >> code;
         if (iss.rdstate() == ios::eofbit && code <= PT::MAX_CODE) {
-            return (code);
+            ret_code = code;
+            return (true);
         }
     }
-    isc_throw(ET, "Unrecognized RR parameter string: " + code_str);
+
+    return (false);
 }
 
 template <typename PT, typename MC>
@@ -475,10 +478,12 @@ RRParamRegistry::removeType(uint16_t code) {
                                                      impl_->str2typemap));
 }
 
-uint16_t
-RRParamRegistry::textToTypeCode(const string& type_string) const {
-    return (textToCode<RRTypeParam, StrRRTypeMap,
-            InvalidRRType>(type_string, impl_->str2typemap));
+bool
+RRParamRegistry::textToTypeCode(const string& type_string,
+                                uint16_t& type_code) const
+{
+    return (textToCode<RRTypeParam, StrRRTypeMap>
+            (type_string, impl_->str2typemap, type_code));
 }
 
 string
@@ -499,10 +504,12 @@ RRParamRegistry::removeClass(uint16_t code) {
                                                        impl_->str2classmap));
 }
 
-uint16_t
-RRParamRegistry::textToClassCode(const string& class_string) const {
-    return (textToCode<RRClassParam, StrRRClassMap,
-            InvalidRRClass>(class_string, impl_->str2classmap));
+bool
+RRParamRegistry::textToClassCode(const string& class_string,
+                                 uint16_t& class_code) const
+{
+    return (textToCode<RRClassParam, StrRRClassMap>
+            (class_string, impl_->str2classmap, class_code));
 }
 
 string
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index 56ae981..bf86436 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -384,16 +384,22 @@ public:
     /// \brief Convert a textual representation of an RR type to the
     /// corresponding 16-bit integer code.
     ///
-    /// This method searches the \c RRParamRegistry for the mapping from the
-    /// given textual representation of RR type to the corresponding integer
-    /// code.  If a mapping is found, it returns the associated type code;
-    /// otherwise, if the given string is in the form of "TYPEnnnn", it returns
-    /// the corresponding number as the type code; otherwise, it throws an
-    /// exception of class \c InvalidRRType.
+    /// This method searches the \c RRParamRegistry for the mapping from
+    /// the given textual representation of RR type to the corresponding
+    /// integer code. If a mapping is found, it returns true with the
+    /// associated type code in \c type_code; otherwise, if the given
+    /// string is in the form of "TYPEnnnn", it returns true with the
+    /// corresponding number as the type code in \c type_code;
+    /// otherwise, it returns false and \c type_code is untouched.
+    ///
+    /// It returns \c false and avoids throwing an exception in the case
+    /// of an error to avoid the exception overhead in some situations.
     ///
     /// \param type_string The textual representation of the RR type.
-    /// \return The RR type code for \c type_string.
-    uint16_t textToTypeCode(const std::string& type_string) const;
+    /// \param type_code Returns the RR type code in this argument.
+    /// \return true if conversion is successful, false otherwise.
+    bool textToTypeCode(const std::string& type_string,
+                        uint16_t& type_code) const;
 
     /// \brief Convert type code into its textual representation.
     ///
@@ -415,16 +421,23 @@ public:
     /// \brief Convert a textual representation of an RR class to the
     /// corresponding 16-bit integer code.
     ///
-    /// This method searches the \c RRParamRegistry for the mapping from the
-    /// given textual representation of RR class to the corresponding integer
-    /// code.  If a mapping is found, it returns the associated class code;
-    /// otherwise, if the given string is in the form of "CLASSnnnn", it returns
-    /// the corresponding number as the class code; otherwise, it throws an
-    /// exception of class \c InvalidRRClass.
+    /// This method searches the \c RRParamRegistry for the mapping from
+    /// the given textual representation of RR class to the
+    /// corresponding integer code.  If a mapping is found, it returns
+    /// true with the associated class code in \c class_code; otherwise,
+    /// if the given string is in the form of "CLASSnnnn", it returns
+    /// true with the corresponding number as the class code in
+    /// \c class_code; otherwise, it returns false and \c class_code is
+    /// untouched.
+    ///
+    /// It returns \c false and avoids throwing an exception in the case
+    /// of an error to avoid the exception overhead in some situations.
     ///
     /// \param class_string The textual representation of the RR class.
-    /// \return The RR class code for \c class_string.
-    uint16_t textToClassCode(const std::string& class_string) const;
+    /// \param class_code Returns the RR class code in this argument.
+    /// \return true if conversion is successful, false otherwise.
+    bool textToClassCode(const std::string& class_string,
+                         uint16_t& class_code) const;
 
     /// \brief Convert class code into its textual representation.
     ///
diff --git a/src/lib/dns/rrset_collection.cc b/src/lib/dns/rrset_collection.cc
new file mode 100644
index 0000000..8711c3f
--- /dev/null
+++ b/src/lib/dns/rrset_collection.cc
@@ -0,0 +1,128 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/master_loader.h>
+#include <dns/rrcollator.h>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/bind.hpp>
+
+using namespace isc;
+
+namespace isc {
+namespace dns {
+
+void
+RRsetCollection::loaderCallback(const std::string&, size_t, const std::string&)
+{
+     // We just ignore callbacks for errors and warnings.
+}
+
+void
+RRsetCollection::addRRset(RRsetPtr rrset) {
+    const CollectionKey key(rrset->getClass(), rrset->getType(),
+                            rrset->getName());
+    CollectionMap::const_iterator it = rrsets_.find(key);
+    if (it != rrsets_.end()) {
+        isc_throw(InvalidParameter,
+                  "RRset for " << rrset->getName() << "/" << rrset->getClass()
+                  << " with type " << rrset->getType() << " already exists");
+    }
+
+    rrsets_.insert(std::pair<CollectionKey, RRsetPtr>(key, rrset));
+}
+
+template<typename T>
+void
+RRsetCollection::constructHelper(T source, const isc::dns::Name& origin,
+                                 const isc::dns::RRClass& rrclass)
+{
+    RRCollator collator(boost::bind(&RRsetCollection::addRRset, this, _1));
+    MasterLoaderCallbacks callbacks
+        (boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3),
+         boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3));
+    MasterLoader loader(source, origin, rrclass, callbacks,
+                        collator.getCallback(),
+                        MasterLoader::DEFAULT);
+    loader.load();
+    collator.flush();
+}
+
+RRsetCollection::RRsetCollection(const char* filename, const Name& origin,
+                                 const RRClass& rrclass)
+{
+    constructHelper(filename, origin, rrclass);
+}
+
+RRsetCollection::RRsetCollection(std::istream& input_stream, const Name& origin,
+                                 const RRClass& rrclass)
+{
+    constructHelper<std::istream&>(input_stream, origin, rrclass);
+}
+
+RRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+                      const RRType& rrtype) {
+    const CollectionKey key(rrclass, rrtype, name);
+    CollectionMap::iterator it = rrsets_.find(key);
+    if (it != rrsets_.end()) {
+        return (it->second);
+    }
+    return (RRsetPtr());
+}
+
+ConstRRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+                      const RRType& rrtype) const
+{
+    const CollectionKey key(rrclass, rrtype, name);
+    CollectionMap::const_iterator it = rrsets_.find(key);
+    if (it != rrsets_.end()) {
+        return (it->second);
+    }
+    return (ConstRRsetPtr());
+}
+
+bool
+RRsetCollection::removeRRset(const Name& name, const RRClass& rrclass,
+                             const RRType& rrtype)
+{
+    const CollectionKey key(rrclass, rrtype, name);
+
+    CollectionMap::iterator it = rrsets_.find(key);
+    if (it == rrsets_.end()) {
+        return (false);
+    }
+
+    rrsets_.erase(it);
+    return (true);
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getBeginning() {
+    CollectionMap::iterator it = rrsets_.begin();
+    return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getEnd() {
+    CollectionMap::iterator it = rrsets_.end();
+    return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/rrset_collection.h b/src/lib/dns/rrset_collection.h
new file mode 100644
index 0000000..62dd9a9
--- /dev/null
+++ b/src/lib/dns/rrset_collection.h
@@ -0,0 +1,172 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_H
+#define RRSET_COLLECTION_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+
+#include <boost/tuple/tuple.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <map>
+
+namespace isc {
+namespace dns {
+
+/// \brief libdns++ implementation of RRsetCollectionBase using an STL
+/// container.
+class RRsetCollection : public RRsetCollectionBase {
+public:
+    /// \brief Constructor.
+    ///
+    /// This constructor creates an empty collection without any data in
+    /// it. RRsets can be added to the collection with the \c addRRset()
+    /// method.
+    RRsetCollection() {}
+
+    /// \brief Constructor.
+    ///
+    /// The \c origin and \c rrclass arguments are required for the zone
+    /// loading, but \c RRsetCollection itself does not do any
+    /// validation, and the collection of RRsets does not have to form a
+    /// valid zone.
+    ///
+    /// \throws MasterLoaderError if there is an error during loading.
+    /// \param filename Name of a file containing a collection of RRs in
+    /// the master file format (which may or may not form a valid zone).
+    /// \param origin The zone origin.
+    /// \param rrclass The zone class.
+    RRsetCollection(const char* filename, const isc::dns::Name& origin,
+                    const isc::dns::RRClass& rrclass);
+
+    /// \brief Constructor.
+    ///
+    /// This constructor is similar to the previous one, but instead of
+    /// taking a filename to load a zone from, it takes an input
+    /// stream.
+    ///
+    /// \throws MasterLoaderError if there is an error during loading.
+    /// \param input_stream The input stream to load from.
+    /// \param origin The zone origin.
+    /// \param rrclass The zone class.
+    RRsetCollection(std::istream& input_stream, const isc::dns::Name& origin,
+                    const isc::dns::RRClass& rrclass);
+
+    /// \brief Destructor
+    virtual ~RRsetCollection() {}
+
+    /// \brief Add an RRset to the collection.
+    ///
+    /// Does not do any validation whether \c rrset belongs to a
+    /// particular zone or not. A reference to \c rrset is taken in an
+    /// internally managed \c shared_ptr, so even if the caller's
+    /// \c RRsetPtr is destroyed, the RRset it wrapped is still alive
+    /// and managed by the \c RRsetCollection. It throws an
+    /// \c isc::InvalidParameter exception if an rrset with the same
+    /// class, type and name already exists.
+    ///
+    /// Callers must not modify the RRset after adding it to the
+    /// collection, as the rrset is indexed internally by the
+    /// collection.
+    void addRRset(isc::dns::RRsetPtr rrset);
+
+    /// \brief Remove an RRset from the collection.
+    ///
+    /// RRset(s) matching the \c name, \c rrclass and \c rrtype are
+    /// removed from the collection.
+    ///
+    /// \return \c true if a matching RRset was deleted, \c false if no
+    /// such RRset exists.
+    bool removeRRset(const isc::dns::Name& name,
+                     const isc::dns::RRClass& rrclass,
+                     const isc::dns::RRType& rrtype);
+
+    /// \brief Find a matching RRset in the collection.
+    ///
+    /// Returns the RRset in the collection that exactly matches the
+    /// given \c name, \c rrclass and \c rrtype.  If no matching RRset
+    /// is found, \c NULL is returned.
+    ///
+    /// \param name The name of the RRset to search for.
+    /// \param rrclass The class of the RRset to search for.
+    /// \param rrtype The type of the RRset to search for.
+    /// \return The RRset if found, \c NULL otherwise.
+    virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+                                         const isc::dns::RRClass& rrclass,
+                                         const isc::dns::RRType& rrtype) const;
+
+    /// \brief Find a matching RRset in the collection (non-const
+    /// variant).
+    ///
+    /// See above for a description of the method and arguments.
+    isc::dns::RRsetPtr find(const isc::dns::Name& name,
+                            const isc::dns::RRClass& rrclass,
+                            const isc::dns::RRType& rrtype);
+
+private:
+    template<typename T>
+    void constructHelper(T source, const isc::dns::Name& origin,
+                         const isc::dns::RRClass& rrclass);
+    void loaderCallback(const std::string&, size_t, const std::string&);
+
+    typedef boost::tuple<isc::dns::RRClass, isc::dns::RRType, isc::dns::Name>
+        CollectionKey;
+    typedef std::map<CollectionKey, isc::dns::RRsetPtr> CollectionMap;
+
+    CollectionMap rrsets_;
+
+protected:
+    class DnsIter : public RRsetCollectionBase::Iter {
+    public:
+        DnsIter(CollectionMap::iterator& iter) :
+            iter_(iter)
+        {}
+
+        virtual const isc::dns::AbstractRRset& getValue() {
+            isc::dns::RRsetPtr& rrset = iter_->second;
+            return (*rrset);
+        }
+
+        virtual IterPtr getNext() {
+            CollectionMap::iterator it = iter_;
+            ++it;
+            return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+        }
+
+        virtual bool equals(Iter& other) {
+            const DnsIter* other_real = dynamic_cast<DnsIter*>(&other);
+            if (other_real == NULL) {
+                return (false);
+            }
+            return (iter_ == other_real->iter_);
+        }
+
+    private:
+        CollectionMap::iterator iter_;
+    };
+
+    virtual RRsetCollectionBase::IterPtr getBeginning();
+    virtual RRsetCollectionBase::IterPtr getEnd();
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif  // RRSET_COLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
new file mode 100644
index 0000000..27e46d1
--- /dev/null
+++ b/src/lib/dns/rrset_collection_base.h
@@ -0,0 +1,163 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_BASE_H
+#define RRSET_COLLECTION_BASE_H 1
+
+#include <dns/rrset.h>
+#include <dns/name.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <iterator>
+
+namespace isc {
+namespace dns {
+
+/// \brief Generic class to represent a set of RRsets.
+///
+/// This is a generic container and the stored set of RRsets does not
+/// necessarily form a valid zone (e.g. there doesn't necessarily have
+/// to be an SOA at the "origin"). Instead, it will be used to represent
+/// a single zone for the purpose of zone loading/checking. It provides
+/// a simple find() method to find an RRset for the given name and type
+/// (and maybe class) and a way to iterate over all RRsets.
+///
+/// See \c RRsetCollection for a simple libdns++ implementation using an
+/// STL container. libdatasrc will have another implementation.
+class RRsetCollectionBase {
+public:
+    /// \brief Find a matching RRset in the collection.
+    ///
+    /// Returns the RRset in the collection that exactly matches the
+    /// given \c name, \c rrclass and \c rrtype.  If no matching RRset
+    /// is found, \c NULL is returned.
+    ///
+    /// \param name The name of the RRset to search for.
+    /// \param rrtype The type of the RRset to search for.
+    /// \param rrclass The class of the RRset to search for.
+    /// \return The RRset if found, \c NULL otherwise.
+    virtual isc::dns::ConstRRsetPtr find
+        (const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+         const isc::dns::RRType& rrtype)
+        const = 0;
+
+    /// \brief Destructor
+    virtual ~RRsetCollectionBase() {}
+
+protected:
+    class Iter; // forward declaration
+
+    /// \brief Wraps Iter with a reference count.
+    typedef boost::shared_ptr<Iter> IterPtr;
+
+    /// \brief A helper iterator interface for \c RRsetCollectionBase.
+    ///
+    /// This is a protected iterator class that is a helper interface
+    /// used by the public iterator.  Derived classes of
+    /// \c RRsetCollectionBase are supposed to implement this class and
+    /// the \c getBeginning() and \c getEnd() methods, so that the
+    /// public interator interface can be provided. This is a forward
+    /// iterator only.
+    class Iter {
+    public:
+        /// \brief Returns the \c AbstractRRset currently pointed to by
+        /// the iterator.
+        virtual const isc::dns::AbstractRRset& getValue() = 0;
+
+        /// \brief Returns an \c IterPtr wrapping an Iter pointing to
+        /// the next \c AbstractRRset in sequence in the collection.
+        virtual IterPtr getNext() = 0;
+
+        /// \brief Check if another iterator is equal to this one.
+        ///
+        /// Returns \c true if this iterator is equal to \c other,
+        /// \c false otherwise. Note that if \c other is not the same
+        /// type as \c this, or cannot be compared meaningfully, the
+        /// method must return \c false.
+        ///
+        /// \param other The other iterator to compare against.
+        /// \return \c true if equal, \c false otherwise.
+        virtual bool equals(Iter& other) = 0;
+    };
+
+    /// \brief Returns an \c IterPtr wrapping an Iter pointing to the
+    /// beginning of the collection.
+    virtual IterPtr getBeginning() = 0;
+
+    /// \brief Returns an \c IterPtr wrapping an Iter pointing past the
+    /// end of the collection.
+    virtual IterPtr getEnd() = 0;
+
+public:
+    /// \brief A forward \c std::iterator for \c RRsetCollectionBase.
+    ///
+    /// It behaves like a \c std::iterator forward iterator, so please
+    /// see its documentation for usage.
+    class Iterator : std::iterator<std::forward_iterator_tag,
+                                   const isc::dns::AbstractRRset>
+    {
+    public:
+        explicit Iterator(IterPtr iter) :
+            iter_(iter)
+        {}
+
+        reference operator*() {
+            return (iter_->getValue());
+        }
+
+        Iterator& operator++() {
+            iter_ = iter_->getNext();
+            return (*this);
+        }
+
+        Iterator operator++(int) {
+            Iterator tmp(iter_);
+            ++*this;
+            return (tmp);
+        }
+
+        bool operator==(const Iterator& other) const {
+            return (iter_->equals(*other.iter_));
+        }
+
+        bool operator!=(const Iterator& other) const {
+            return (!iter_->equals(*other.iter_));
+        }
+
+    private:
+        IterPtr iter_;
+    };
+
+    /// \brief Returns an iterator pointing to the beginning of the
+    /// collection.
+    Iterator begin() {
+      return Iterator(getBeginning());
+    }
+
+    /// \brief Returns an iterator pointing past the end of the
+    /// collection.
+    Iterator end() {
+      return Iterator(getEnd());
+    }
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif  // RRSET_COLLECTION_BASE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
index 7904d84..23d57f4 100644
--- a/src/lib/dns/rrttl.h
+++ b/src/lib/dns/rrttl.h
@@ -133,7 +133,7 @@ public:
     /// One main purpose of this function is to minimize the overhead
     /// when the given text does not represent a valid RR TTL.  For this
     /// reason this function intentionally omits the capability of delivering
-    /// details reason for the parse failure, such as in the \c want()
+    /// a detailed reason for the parse failure, such as in the \c want()
     /// string when exception is thrown from the constructor (it will
     /// internally require a creation of string object, which is relatively
     /// expensive).  If such detailed information is necessary, the constructor
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
index 4ef4e67..84892bf 100644
--- a/src/lib/dns/rrtype.cc
+++ b/src/lib/dns/rrtype.cc
@@ -31,8 +31,11 @@ using isc::dns::RRType;
 namespace isc {
 namespace dns {
 
-RRType::RRType(const std::string& typestr) {
-    typecode_ = RRParamRegistry::getRegistry().textToTypeCode(typestr);
+RRType::RRType(const std::string& type_str) {
+    if (!RRParamRegistry::getRegistry().textToTypeCode(type_str, typecode_)) {
+        isc_throw(InvalidRRType,
+                  "Unrecognized RR type string: " + type_str);
+    }
 }
 
 RRType::RRType(InputBuffer& buffer) {
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index df42720..8d32b42 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -75,6 +75,8 @@ run_unittests_SOURCES += tsigkey_unittest.cc
 run_unittests_SOURCES += tsigrecord_unittest.cc
 run_unittests_SOURCES += character_string_unittest.cc
 run_unittests_SOURCES += master_loader_callbacks_test.cc
+run_unittests_SOURCES += rrset_collection_unittest.cc
+run_unittests_SOURCES += zone_checker_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 # We shouldn't need to include BOTAN_LIBS here, but there
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 89a1440..051c662 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -391,7 +391,8 @@ struct ErrorCase {
       "Missing Rdata" },
 
     { "www      3600    IN", NULL, "Unexpected EOLN" },
-    { "www      3600    CH  TXT nothing", NULL, "Class mismatch" },
+    { "www      3600    CH  TXT nothing", "Class mismatch: CH vs. IN",
+      "Class mismatch" },
     { "www      \"3600\"  IN  A   192.0.2.1", NULL, "Quoted TTL" },
     { "www      3600    \"IN\"  A   192.0.2.1", NULL, "Quoted class" },
     { "www      3600    IN  \"A\"   192.0.2.1", NULL, "Quoted type" },
diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc
index 9d23622..44bc979 100644
--- a/src/lib/dns/tests/rdata_char_string_unittest.cc
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -25,7 +25,8 @@
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using isc::dns::rdata::generic::detail::CharString;
-using isc::dns::rdata::generic::detail::strToCharString;
+using isc::dns::rdata::generic::detail::stringToCharString;
+using isc::dns::rdata::generic::detail::charStringToString;
 using isc::util::unittests::matchWireData;
 
 namespace {
@@ -60,19 +61,19 @@ createStringRegion(const std::string& str) {
 TEST_F(CharStringTest, normalConversion) {
     uint8_t tmp[3];             // placeholder for expected sequence
 
-    strToCharString(str_region, chstr);
+    stringToCharString(str_region, chstr);
     matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
 
     // Empty string
     chstr.clear();
-    strToCharString(createStringRegion(""), chstr);
+    stringToCharString(createStringRegion(""), chstr);
     tmp[0] = 0;
     matchWireData(tmp, 1, &chstr[0], chstr.size());
 
     // Possible largest char string
     chstr.clear();
     std::string long_str(255, 'x');
-    strToCharString(createStringRegion(long_str), chstr);
+    stringToCharString(createStringRegion(long_str), chstr);
     std::vector<uint8_t> expected;
     expected.push_back(255);    // len of char string
     expected.insert(expected.end(), long_str.begin(), long_str.end());
@@ -83,32 +84,32 @@ TEST_F(CharStringTest, normalConversion) {
     chstr.clear();
     long_str.at(254) = '\\';    // replace the last 'x' with '\'
     long_str.append("120");     // 'x' = 120
-    strToCharString(createStringRegion(long_str), chstr);
+    stringToCharString(createStringRegion(long_str), chstr);
     matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
 
     // Escaped '\'
     chstr.clear();
     tmp[0] = 1;
     tmp[1] = '\\';
-    strToCharString(createStringRegion("\\\\"), chstr);
+    stringToCharString(createStringRegion("\\\\"), chstr);
     matchWireData(tmp, 2, &chstr[0], chstr.size());
 
     // Boundary values for \DDD
     chstr.clear();
     tmp[0] = 1;
     tmp[1] = 0;
-    strToCharString(createStringRegion("\\000"), chstr);
+    stringToCharString(createStringRegion("\\000"), chstr);
     matchWireData(tmp, 2, &chstr[0], chstr.size());
 
     chstr.clear();
-    strToCharString(createStringRegion("\\255"), chstr);
+    stringToCharString(createStringRegion("\\255"), chstr);
     tmp[0] = 1;
     tmp[1] = 255;
     matchWireData(tmp, 2, &chstr[0], chstr.size());
 
     // Another digit follows DDD; it shouldn't cause confusion
     chstr.clear();
-    strToCharString(createStringRegion("\\2550"), chstr);
+    stringToCharString(createStringRegion("\\2550"), chstr);
     tmp[0] = 2;                 // string len is now 2
     tmp[2] = '0';
     matchWireData(tmp, 3, &chstr[0], chstr.size());
@@ -116,13 +117,13 @@ TEST_F(CharStringTest, normalConversion) {
 
 TEST_F(CharStringTest, badConversion) {
     // string cannot exceed 255 bytes
-    EXPECT_THROW(strToCharString(createStringRegion(std::string(256, 'a')),
-                                 chstr),
+    EXPECT_THROW(stringToCharString(createStringRegion(std::string(256, 'a')),
+                                    chstr),
                  CharStringTooLong);
 
     // input string ending with (non escaped) '\'
     chstr.clear();
-    EXPECT_THROW(strToCharString(createStringRegion("foo\\"), chstr),
+    EXPECT_THROW(stringToCharString(createStringRegion("foo\\"), chstr),
                  InvalidRdataText);
 }
 
@@ -130,18 +131,44 @@ TEST_F(CharStringTest, badDDD) {
     // Check various type of bad form of \DDD
 
     // Not a number
-    EXPECT_THROW(strToCharString(createStringRegion("\\1a2"), chstr),
+    EXPECT_THROW(stringToCharString(createStringRegion("\\1a2"), chstr),
                  InvalidRdataText);
-    EXPECT_THROW(strToCharString(createStringRegion("\\12a"), chstr),
+    EXPECT_THROW(stringToCharString(createStringRegion("\\12a"), chstr),
                  InvalidRdataText);
 
     // Not in the range of uint8_t
-    EXPECT_THROW(strToCharString(createStringRegion("\\256"), chstr),
+    EXPECT_THROW(stringToCharString(createStringRegion("\\256"), chstr),
                  InvalidRdataText);
 
     // Short buffer
-    EXPECT_THROW(strToCharString(createStringRegion("\\42"), chstr),
+    EXPECT_THROW(stringToCharString(createStringRegion("\\42"), chstr),
                  InvalidRdataText);
 }
 
+const struct TestData {
+    const char *data;
+    const char *expected;
+} conversion_data[] = {
+    {"Test\"Test", "Test\\\"Test"},
+    {"Test;Test", "Test\\;Test"},
+    {"Test\\Test", "Test\\\\Test"},
+    {"Test\x1fTest", "Test\\031Test"},
+    {"Test ~ Test", "Test ~ Test"},
+    {"Test\x7fTest", "Test\\127Test"},
+    {NULL, NULL}
+};
+
+TEST_F(CharStringTest, charStringToString) {
+    for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+        uint8_t idata[32];
+        size_t length = std::strlen(cur->data);
+        // length (1 byte) + string (length bytes)
+        assert(sizeof(idata) > length);
+        idata[0] = static_cast<uint8_t>(length);
+        std::memcpy(idata + 1, cur->data, length);
+        const CharString test_data(idata, idata + length + 1);
+        EXPECT_EQ(cur->expected, charStringToString(test_data));
+    }
+}
+
 } // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index d045875..ef9bdfe 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -334,6 +334,24 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
     EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
+    EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText());
+    EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText());
+
+    // Check escape behavior
+    const TypeParam double_quotes("Test-String\"Test-String\"");
+    EXPECT_EQ("\"Test-String\\\"Test-String\\\"\"", double_quotes.toText());
+    const TypeParam semicolon("Test-String\\;Test-String");
+    EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
+    const TypeParam backslash("Test-String\\\\Test-String");
+    EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText());
+    const TypeParam before_x20("Test-String\\031Test-String");
+    EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText());
+    const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\"");
+    EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText());
+    const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String");
+    EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText());
+    const TypeParam after_x7e("Test-String\\127Test-String");
+    EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText());
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index 6156be5..11f1c54 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -59,7 +59,7 @@ RRClassTest::rrclassFactoryFromWire(const char* datafile) {
     return (RRClass(buffer));
 }
 
-TEST_F(RRClassTest, fromText) {
+TEST_F(RRClassTest, fromTextConstructor) {
     EXPECT_EQ("IN", RRClass("IN").toText());
     EXPECT_EQ("CH", RRClass("CH").toText());
 
@@ -96,6 +96,14 @@ TEST_F(RRClassTest, toText) {
     EXPECT_EQ("CLASS65000", RRClass(65000).toText());
 }
 
+TEST_F(RRClassTest, createFromText) {
+    const MaybeRRClass rrclass("IN");
+    EXPECT_TRUE(rrclass);
+    EXPECT_EQ("IN", rrclass->toText());
+    EXPECT_TRUE(RRClass::createFromText("CH"));
+    EXPECT_FALSE(RRClass::createFromText("ZZ"));
+}
+
 TEST_F(RRClassTest, toWireBuffer) {
     rrclass_1.toWire(obuffer);
     rrclass_0x80.toWire(obuffer);
diff --git a/src/lib/dns/tests/rrset_collection_unittest.cc b/src/lib/dns/tests/rrset_collection_unittest.cc
new file mode 100644
index 0000000..e17e9e7
--- /dev/null
+++ b/src/lib/dns/tests/rrset_collection_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <gtest/gtest.h>
+
+#include <list>
+#include <fstream>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace std;
+
+namespace {
+
+class RRsetCollectionTest : public ::testing::Test {
+public:
+    RRsetCollectionTest() :
+        rrclass("IN"),
+        origin("example.org"),
+        collection(TEST_DATA_SRCDIR "/example.org", origin, rrclass)
+    {}
+
+    const RRClass rrclass;
+    const Name origin;
+    RRsetCollection collection;
+};
+
+TEST_F(RRsetCollectionTest, istreamConstructor) {
+    std::ifstream fs(TEST_DATA_SRCDIR "/example.org");
+    RRsetCollection collection2(fs, origin, rrclass);
+
+    RRsetCollectionBase::Iterator iter = collection.begin();
+    RRsetCollectionBase::Iterator iter2 = collection2.begin();
+    while (iter != collection.end()) {
+         ASSERT_TRUE(iter2 != collection2.end());
+         EXPECT_EQ((*iter).toText(), (*iter2).toText());
+         ++iter;
+         ++iter2;
+    }
+    ASSERT_TRUE(iter2 == collection2.end());
+}
+
+template <typename T, typename TP>
+void doFind(T& collection, const RRClass& rrclass) {
+    // Test the find() that returns ConstRRsetPtr
+    TP rrset = collection.find(Name("www.example.org"), rrclass, RRType::A());
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+    EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+    EXPECT_EQ(RRClass("IN"), rrset->getClass());
+    EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+    // foo.example.org doesn't exist
+    rrset = collection.find(Name("foo.example.org"), rrclass, RRType::A());
+    EXPECT_FALSE(rrset);
+
+    // www.example.org exists, but not with MX
+    rrset = collection.find(Name("www.example.org"), rrclass, RRType::MX());
+    EXPECT_FALSE(rrset);
+
+    // www.example.org exists, with AAAA
+    rrset = collection.find(Name("www.example.org"), rrclass, RRType::AAAA());
+    EXPECT_TRUE(rrset);
+
+    // www.example.org with AAAA does not exist in RRClass::CH()
+    rrset = collection.find(Name("www.example.org"), RRClass::CH(),
+                            RRType::AAAA());
+    EXPECT_FALSE(rrset);
+}
+
+TEST_F(RRsetCollectionTest, findConst) {
+    // Test the find() that returns ConstRRsetPtr
+    const RRsetCollection& ccln = collection;
+    doFind<const RRsetCollection, ConstRRsetPtr>(ccln, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, find) {
+    // Test the find() that returns RRsetPtr
+    doFind<RRsetCollection, RRsetPtr>(collection, rrclass);
+}
+
+void
+doAddAndRemove(RRsetCollection& collection, const RRClass& rrclass) {
+    // foo.example.org/A doesn't exist
+    RRsetPtr rrset_found = collection.find(Name("foo.example.org"), rrclass,
+                                           RRType::A());
+    EXPECT_FALSE(rrset_found);
+
+    // Add foo.example.org/A
+    RRsetPtr rrset(new BasicRRset(Name("foo.example.org"), rrclass, RRType::A(),
+                                  RRTTL(7200)));
+    rrset->addRdata(in::A("192.0.2.1"));
+    collection.addRRset(rrset);
+
+    // foo.example.org/A should now exist
+    rrset_found = collection.find(Name("foo.example.org"), rrclass,
+                                  RRType::A());
+    EXPECT_TRUE(rrset_found);
+    EXPECT_EQ(RRType::A(), rrset_found->getType());
+    EXPECT_EQ(RRTTL(7200), rrset_found->getTTL());
+    EXPECT_EQ(RRClass("IN"), rrset_found->getClass());
+    EXPECT_EQ(Name("foo.example.org"), rrset_found->getName());
+
+    // The collection must not be empty.
+    EXPECT_TRUE(collection.end() != collection.begin());
+
+    // Adding a duplicate RRset must throw.
+    EXPECT_THROW({
+        collection.addRRset(rrset);
+    }, isc::InvalidParameter);
+
+    // Remove foo.example.org/A, which should pass
+    EXPECT_TRUE(collection.removeRRset(Name("foo.example.org"),
+                                       rrclass, RRType::A()));
+    // foo.example.org/A should not exist now
+    rrset_found = collection.find(Name("foo.example.org"), rrclass,
+                                  RRType::A());
+    EXPECT_FALSE(rrset_found);
+
+    // Removing foo.example.org/A should fail now
+    EXPECT_FALSE(collection.removeRRset(Name("foo.example.org"),
+                                        rrclass, RRType::A()));
+}
+
+TEST_F(RRsetCollectionTest, addAndRemove) {
+    doAddAndRemove(collection, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, empty) {
+    RRsetCollection cln;
+
+    // Here, cln is empty.
+    EXPECT_TRUE(cln.end() == cln.begin());
+
+    doAddAndRemove(cln, rrclass);
+
+    // cln should be empty again here, after the add and remove
+    // operations.
+    EXPECT_TRUE(cln.end() == cln.begin());
+}
+
+TEST_F(RRsetCollectionTest, iteratorTest) {
+    // The collection must not be empty.
+    EXPECT_TRUE(collection.end() != collection.begin());
+
+    // Here, we just count the records and do some basic tests on them.
+    size_t count = 0;
+    for (RRsetCollection::Iterator it = collection.begin();
+         it != collection.end(); ++it) {
+         ++count;
+         const AbstractRRset& rrset = *it;
+         EXPECT_EQ(rrclass, rrset.getClass());
+         EXPECT_EQ(RRTTL(3600), rrset.getTTL());
+    }
+
+    // example.org master file has SOA, NS, A, AAAA
+    EXPECT_EQ(4, count);
+}
+
+// This is a dummy class which is used in iteratorCompareDifferent test
+// to compare iterators from different RRsetCollectionBase
+// implementations.
+class MyRRsetCollection : public RRsetCollectionBase {
+public:
+    MyRRsetCollection()
+    {}
+
+    virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name&,
+                                         const isc::dns::RRClass&,
+                                         const isc::dns::RRType&) const {
+        return (ConstRRsetPtr());
+    }
+
+    typedef std::list<isc::dns::RRset> MyCollection;
+
+protected:
+    class MyIter : public RRsetCollectionBase::Iter {
+    public:
+        MyIter(MyCollection::iterator& iter) :
+            iter_(iter)
+        {}
+
+        virtual const isc::dns::AbstractRRset& getValue() {
+            return (*iter_);
+        }
+
+        virtual IterPtr getNext() {
+            MyCollection::iterator it = iter_;
+            ++it;
+            return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+        }
+
+        virtual bool equals(Iter& other) {
+            const MyIter* other_real = dynamic_cast<MyIter*>(&other);
+            if (other_real == NULL) {
+                return (false);
+            }
+            return (iter_ == other_real->iter_);
+        }
+
+    private:
+        MyCollection::iterator iter_;
+    };
+
+    virtual RRsetCollectionBase::IterPtr getBeginning() {
+        MyCollection::iterator it = dummy_list_.begin();
+        return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+    }
+
+    virtual RRsetCollectionBase::IterPtr getEnd() {
+        MyCollection::iterator it = dummy_list_.end();
+        return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+    }
+
+private:
+    MyCollection dummy_list_;
+};
+
+TEST_F(RRsetCollectionTest, iteratorCompareDifferent) {
+    // Create objects of two different RRsetCollectionBase
+    // implementations.
+    RRsetCollection cln1;
+    MyRRsetCollection cln2;
+
+    // Comparing two iterators from different RRsetCollectionBase
+    // implementations must not throw.
+    EXPECT_TRUE(cln2.begin() != cln1.begin());
+    EXPECT_TRUE(cln1.end() != cln2.end());
+}
+
+} // namespace
diff --git a/src/lib/dns/tests/zone_checker_unittest.cc b/src/lib/dns/tests/zone_checker_unittest.cc
new file mode 100644
index 0000000..dbe204d
--- /dev/null
+++ b/src/lib/dns/tests/zone_checker_unittest.cc
@@ -0,0 +1,352 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrset_collection.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using isc::Unexpected;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+const char* const soa_txt = "ns.example.com. root.example.com. 0 0 0 0 0";
+const char* const ns_txt1 = "ns.example.com.";
+const char* const ns_a_txt1 = "192.0.2.1";
+const char* const ns_txt2 = "ns2.example.com.";
+const char* const ns_a_txt2 = "192.0.2.2";
+
+class ZoneCheckerTest : public ::testing::Test {
+protected:
+    ZoneCheckerTest() :
+        zname_("example.com"), zclass_(RRClass::IN()),
+        soa_(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60))),
+        ns_(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60))),
+        callbacks_(boost::bind(&ZoneCheckerTest::callback, this, _1, true),
+                   boost::bind(&ZoneCheckerTest::callback, this, _1, false))
+    {
+        std::stringstream ss;
+        ss << "example.com. 60 IN SOA " << soa_txt << "\n";
+        ss << "example.com. 60 IN NS " << ns_txt1 << "\n";
+        ss << "ns.example.com. 60 IN A " << ns_a_txt1 << "\n";
+        ss << "ns2.example.com. 60 IN A " << ns_a_txt2 << "\n";
+        rrsets_.reset(new RRsetCollection(ss, zname_, zclass_));
+    }
+
+public:
+    // This one is passed to boost::bind.  Some compilers seem to require
+    // it be public.
+    void callback(const std::string& reason, bool is_error) {
+        if (is_error) {
+            errors_.push_back(reason);
+        } else {
+            warns_.push_back(reason);
+        }
+    }
+
+protected:
+    // Check stored issue messages with expected ones.  Clear vectors so
+    // the caller can check other cases.
+    void checkIssues() {
+        EXPECT_EQ(expected_errors_.size(), errors_.size());
+        for (int i = 0; i < std::min(expected_errors_.size(), errors_.size());
+             ++i) {
+            // The actual message should begin with the expected message.
+            EXPECT_EQ(0, errors_[0].find(expected_errors_[0]))
+                << "actual message: " << errors_[0] << " expected: " <<
+                expected_errors_[0];
+        }
+        EXPECT_EQ(expected_warns_.size(), warns_.size());
+        for (int i = 0; i < std::min(expected_warns_.size(), warns_.size());
+             ++i) {
+            EXPECT_EQ(0, warns_[0].find(expected_warns_[0]))
+                << "actual message: " << warns_[0] << " expected: " <<
+                expected_warns_[0];
+        }
+
+        errors_.clear();
+        expected_errors_.clear();
+        warns_.clear();
+        expected_warns_.clear();
+    }
+
+    const Name zname_;
+    const RRClass zclass_;
+    boost::scoped_ptr<RRsetCollection> rrsets_;
+    RRsetPtr soa_;
+    RRsetPtr ns_;
+    std::vector<std::string> errors_;
+    std::vector<std::string> warns_;
+    std::vector<std::string> expected_errors_;
+    std::vector<std::string> expected_warns_;
+    ZoneCheckerCallbacks callbacks_;
+};
+
+TEST_F(ZoneCheckerTest, checkGood) {
+    // Checking a valid case.  No errors or warnings should be reported.
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+
+    // Multiple NS RRs are okay.
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    ns_->addRdata(generic::NS(ns_txt1));
+    ns_->addRdata(generic::NS(ns_txt2));
+    rrsets_->addRRset(ns_);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkSOA) {
+    // If the zone has no SOA it triggers an error.
+    rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN: has 0 SOA records");
+    checkIssues();
+
+    // If null callback is specified, checkZone() only returns the final
+    // result.
+    ZoneCheckerCallbacks noerror_callbacks(
+        NULL, boost::bind(&ZoneCheckerTest::callback, this, _1, false));
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, noerror_callbacks));
+    checkIssues();
+
+    // If there are more than 1 SOA RR, it's also an error.
+    errors_.clear();
+    soa_->addRdata(generic::SOA(soa_txt));
+    soa_->addRdata(generic::SOA("ns2.example.com. . 0 0 0 0 0"));
+    rrsets_->addRRset(soa_);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN: has 2 SOA records");
+    checkIssues();
+
+    // If the SOA RRset is "empty", it's treated as an implementation
+    // (rather than operational) error and results in an exception.
+    rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+    soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+    rrsets_->addRRset(soa_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+
+    // Likewise, if the SOA RRset contains non SOA Rdata, it should be a bug.
+    rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+    soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+    soa_->addRdata(createRdata(RRType::NS(), zclass_, "ns.example.com"));
+    rrsets_->addRRset(soa_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNS) {
+    // If the zone has no NS at origin it triggers an error.
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN: has no NS records");
+    checkIssues();
+
+    // Check two buggy cases like the SOA tests
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    rrsets_->addRRset(ns_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    ns_->addRdata(createRdata(RRType::TXT(), zclass_, "ns.example.com"));
+    rrsets_->addRRset(ns_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNSData) {
+    const Name ns_name("ns.example.com");
+
+    // If a ("in-bailiwick") NS name doesn't have an address record, it's
+    // reported as a warning.
+    rrsets_->removeRRset(ns_name, zclass_, RRType::A());
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_warns_.push_back("zone example.com/IN: NS has no address");
+    checkIssues();
+
+    // Same check, but disabling warning callback.  Same result, but without
+    // the warning.
+    ZoneCheckerCallbacks nowarn_callbacks(
+        boost::bind(&ZoneCheckerTest::callback, this, _1, true), NULL);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, nowarn_callbacks));
+    checkIssues();
+
+    // A tricky case: if the name matches a wildcard, it should technically
+    // be considered valid, but this checker doesn't check that far and still
+    // warns.
+    RRsetPtr wild(new RRset(Name("*.example.com"), zclass_, RRType::A(),
+                            RRTTL(0)));
+    wild->addRdata(in::A("192.0.2.255"));
+    rrsets_->addRRset(wild);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_warns_.push_back("zone example.com/IN: NS has no address");
+    checkIssues();
+
+    // If there's a CNAME at the name instead, it's an error.
+    rrsets_->removeRRset(Name("*.example.com"), zclass_, RRType::A());
+    RRsetPtr cname(new RRset(ns_name, zclass_, RRType::CNAME(), RRTTL(60)));
+    cname->addRdata(generic::CNAME("cname.example.com"));
+    rrsets_->addRRset(cname);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+                               "a CNAME (illegal per RFC2181)");
+    checkIssues();
+
+    // It doesn't have to be A.  An AAAA is enough.
+    rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+    RRsetPtr aaaa(new RRset(ns_name, zclass_, RRType::AAAA(), RRTTL(60)));
+    aaaa->addRdata(in::AAAA("2001:db8::1"));
+    rrsets_->addRRset(aaaa);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+
+    // Coexisting CNAME makes it error (CNAME with other record is itself
+    // invalid, but it's a different issue in this context)
+    rrsets_->addRRset(cname);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+                               "a CNAME (illegal per RFC2181)");
+    checkIssues();
+
+    // It doesn't matter if the NS name is "out of bailiwick".
+    rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    ns_->addRdata(generic::NS("ns.example.org"));
+    rrsets_->addRRset(ns_);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+
+    // Note that if the NS name is the origin name, it should be checked
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    ns_->addRdata(generic::NS(zname_));
+    rrsets_->addRRset(ns_);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_warns_.push_back("zone example.com/IN: NS has no address");
+    checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDelegation) {
+    // Tests various cases where there's a zone cut due to delegation between
+    // the zone origin and the NS name.  In each case the NS name doesn't have
+    // an address record.
+    const Name ns_name("ns.child.example.com");
+
+    // Zone cut due to delegation in the middle; the check for the address
+    // record should be skipped.
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    ns_->addRdata(generic::NS(ns_name));
+    rrsets_->addRRset(ns_);
+    RRsetPtr child_ns(new RRset(Name("child.example.com"), zclass_,
+                                RRType::NS(), RRTTL(60)));
+    child_ns->addRdata(generic::NS("ns.example.org"));
+    rrsets_->addRRset(child_ns);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+
+    // Zone cut at the NS name.  Same result.
+    rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+    child_ns.reset(new RRset(ns_name, zclass_, RRType::NS(), RRTTL(60)));
+    child_ns->addRdata(generic::NS("ns.example.org"));
+    rrsets_->addRRset(child_ns);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+
+    // Zone cut below the NS name.  The check applies.
+    rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+    child_ns.reset(new RRset(Name("another.ns.child.example.com"), zclass_,
+                             RRType::NS(), RRTTL(60)));
+    child_ns->addRdata(generic::NS("ns.example.org"));
+    rrsets_->addRRset(child_ns);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_warns_.push_back("zone example.com/IN: NS has no address");
+    checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDNAME) {
+    // Similar to the above case, but the zone cut is due to DNAME.  This is
+    // an invalid configuration.
+    const Name ns_name("ns.child.example.com");
+
+    // Zone cut due to DNAME at the zone origin.  This is an invalid case.
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    ns_->addRdata(generic::NS(ns_name));
+    rrsets_->addRRset(ns_);
+    RRsetPtr dname(new RRset(zname_, zclass_, RRType::DNAME(), RRTTL(60)));
+    dname->addRdata(generic::DNAME("example.org"));
+    rrsets_->addRRset(dname);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+                               " is below a DNAME 'example.com'");
+    checkIssues();
+
+    // Zone cut due to DNAME in the middle.  Same result.
+    rrsets_->removeRRset(zname_, zclass_, RRType::DNAME());
+    dname.reset(new RRset(Name("child.example.com"), zclass_, RRType::DNAME(),
+                          RRTTL(60)));
+    dname->addRdata(generic::DNAME("example.org"));
+    rrsets_->addRRset(dname);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+                               " is below a DNAME 'child.example.com'");
+    checkIssues();
+
+    // A tricky case: there's also an NS at the name that has DNAME.  It's
+    // prohibited per RFC6672 so we could say it's "undefined".  Nevertheless,
+    // this implementation prefers the NS and skips further checks.
+    ns_.reset(new RRset(Name("child.example.com"), zclass_, RRType::NS(),
+                        RRTTL(60)));
+    ns_->addRdata(generic::NS("ns.example.org"));
+    rrsets_->addRRset(ns_);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+
+    // Zone cut due to DNAME at the NS name.  In this case DNAME doesn't
+    // affect the NS name, so it should result in "no address record" warning.
+    rrsets_->removeRRset(dname->getName(), zclass_, RRType::DNAME());
+    rrsets_->removeRRset(ns_->getName(), zclass_, RRType::NS());
+    dname.reset(new RRset(ns_name, zclass_, RRType::DNAME(), RRTTL(60)));
+    dname->addRdata(generic::DNAME("example.org"));
+    rrsets_->addRRset(dname);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_warns_.push_back("zone example.com/IN: NS has no address");
+    checkIssues();
+}
+
+}
diff --git a/src/lib/dns/zone_checker.cc b/src/lib/dns/zone_checker.cc
new file mode 100644
index 0000000..aa307d2
--- /dev/null
+++ b/src/lib/dns/zone_checker.cc
@@ -0,0 +1,196 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+
+using boost::lexical_cast;
+using std::string;
+
+namespace isc {
+namespace dns {
+
+namespace {
+std::string
+zoneText(const Name& zone_name, const RRClass& zone_class) {
+    return (zone_name.toText(true) + "/" + zone_class.toText());
+}
+
+void
+checkSOA(const Name& zone_name, const RRClass& zone_class,
+         const RRsetCollectionBase& zone_rrsets,
+         ZoneCheckerCallbacks& callback) {
+    ConstRRsetPtr rrset =
+        zone_rrsets.find(zone_name, zone_class, RRType::SOA());
+    size_t count = 0;
+    if (rrset) {
+        for (RdataIteratorPtr rit = rrset->getRdataIterator();
+             !rit->isLast();
+             rit->next(), ++count) {
+            if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
+                NULL) {
+                isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
+            }
+        }
+        if (count == 0) {
+            // this should be an implementation bug, not an operational error.
+            isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
+        }
+    }
+    if (count != 1) {
+        callback.error("zone " + zoneText(zone_name, zone_class) + ": has " +
+                       lexical_cast<string>(count) + " SOA records");
+    }
+}
+
+// Check if a target name is beyond zone cut, either due to delegation or
+// DNAME.  Note that DNAME works on the origin but not on the name itself,
+// while delegation works on the name itself (but the NS at the origin is not
+// delegation).
+ConstRRsetPtr
+findZoneCut(const Name& zone_name, const RRClass& zone_class,
+            const RRsetCollectionBase& zone_rrsets, const Name& target_name) {
+    const unsigned int origin_count = zone_name.getLabelCount();
+    const unsigned int target_count = target_name.getLabelCount();
+    assert(origin_count <= target_count);
+
+    for (unsigned int l = origin_count; l <= target_count; ++l) {
+        const Name& mid_name = (l == target_count) ? target_name :
+            target_name.split(target_count - l);
+
+        ConstRRsetPtr found;
+        if (l != origin_count &&
+            (found = zone_rrsets.find(mid_name, zone_class, RRType::NS())) !=
+            NULL) {
+            return (found);
+        }
+        if (l != target_count &&
+            (found = zone_rrsets.find(mid_name, zone_class, RRType::DNAME()))
+            != NULL) {
+            return (found);
+        }
+    }
+    return (ConstRRsetPtr());
+}
+
+// Check if each "in-zone" NS name has an address record, identifying some
+// error cases.
+void
+checkNSNames(const Name& zone_name, const RRClass& zone_class,
+             const RRsetCollectionBase& zone_rrsets,
+             ConstRRsetPtr ns_rrset, ZoneCheckerCallbacks& callbacks) {
+    if (ns_rrset->getRdataCount() == 0) {
+        // this should be an implementation bug, not an operational error.
+        isc_throw(Unexpected, "Zone checker found an empty NS RRset");
+    }
+
+    for (RdataIteratorPtr rit = ns_rrset->getRdataIterator();
+         !rit->isLast();
+         rit->next()) {
+        const rdata::generic::NS* ns_data =
+            dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent());
+        if (ns_data == NULL) {
+            isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
+        }
+        const Name& ns_name = ns_data->getNSName();
+        const NameComparisonResult::NameRelation reln =
+            ns_name.compare(zone_name).getRelation();
+        if (reln != NameComparisonResult::EQUAL &&
+            reln != NameComparisonResult::SUBDOMAIN) {
+            continue;           // not in the zone.  we can ignore it.
+        }
+
+        // Check if there's a zone cut between the origin and the NS name.
+        ConstRRsetPtr cut_rrset = findZoneCut(zone_name, zone_class,
+                                              zone_rrsets, ns_name);
+        if (cut_rrset) {
+            if  (cut_rrset->getType() == RRType::NS()) {
+                continue; // delegation; making the NS name "out of zone".
+            } else if (cut_rrset->getType() == RRType::DNAME()) {
+                callbacks.error("zone " + zoneText(zone_name, zone_class) +
+                                ": NS '" + ns_name.toText(true) + "' is " +
+                                "below a DNAME '" +
+                                cut_rrset->getName().toText(true) +
+                                "' (illegal per RFC6672)");
+                continue;
+            } else {
+                assert(false);
+            }
+        }
+        if (zone_rrsets.find(ns_name, zone_class, RRType::CNAME()) != NULL) {
+            callbacks.error("zone " + zoneText(zone_name, zone_class) +
+                            ": NS '" + ns_name.toText(true) + "' is a CNAME " +
+                            "(illegal per RFC2181)");
+            continue;
+        }
+        if (zone_rrsets.find(ns_name, zone_class, RRType::A()) == NULL &&
+            zone_rrsets.find(ns_name, zone_class, RRType::AAAA()) == NULL) {
+            callbacks.warn("zone " + zoneText(zone_name, zone_class) +
+                           ": NS has no address records (A or AAAA)");
+        }
+    }
+}
+
+void
+checkNS(const Name& zone_name, const RRClass& zone_class,
+        const RRsetCollectionBase& zone_rrsets,
+        ZoneCheckerCallbacks& callbacks) {
+    ConstRRsetPtr rrset =
+        zone_rrsets.find(zone_name, zone_class, RRType::NS());
+    if (rrset == NULL) {
+        callbacks.error("zone " + zoneText(zone_name, zone_class) +
+                        ": has no NS records");
+        return;
+    }
+    checkNSNames(zone_name, zone_class, zone_rrsets, rrset, callbacks);
+}
+
+// The following is a simple wrapper of checker callback so checkZone()
+// can also remember any critical errors.
+void
+errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
+             bool* had_error) {
+    *had_error = true;
+    callbacks->error(reason);
+}
+}
+
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+          const RRsetCollectionBase& zone_rrsets,
+          const ZoneCheckerCallbacks& callbacks) {
+    bool had_error = false;
+    ZoneCheckerCallbacks my_callbacks(
+        boost::bind(errorWrapper, _1, &callbacks, &had_error),
+        boost::bind(&ZoneCheckerCallbacks::warn, &callbacks, _1));
+
+    checkSOA(zone_name, zone_class, zone_rrsets, my_callbacks);
+    checkNS(zone_name, zone_class, zone_rrsets, my_callbacks);
+
+    return (!had_error);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/zone_checker.h b/src/lib/dns/zone_checker.h
new file mode 100644
index 0000000..dfb4946
--- /dev/null
+++ b/src/lib/dns/zone_checker.h
@@ -0,0 +1,162 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ZONE_CHECKER_H
+#define ZONE_CHECKER_H 1
+
+#include <dns/dns_fwd.h>
+
+#include <boost/function.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dns {
+
+/// \brief Set of callbacks used in zone checks.
+///
+/// Objects of this class are expected to be passed to \c checkZone().
+class ZoneCheckerCallbacks {
+public:
+    /// \brief Functor type of the callback on some issue (error or warning).
+    ///
+    /// Its parameter indicates the reason for the corresponding issue.
+    typedef boost::function<void(const std::string& reason)> IssueCallback;
+
+    /// \brief Constructor.
+    ///
+    /// Either or both of the callbacks can be empty, in which case the
+    /// corresponding callback will be effectively no-operation.  This can be
+    /// used, for example, when the caller of \c checkZone() is only
+    /// interested in the final result.  Note that a \c NULL pointer will be
+    /// implicitly converted to an empty functor object, so passing \c NULL
+    /// suffices.
+    ///
+    /// \throw none
+    ///
+    /// \param error_callback Callback functor to be called on critical errors.
+    /// \param warn_callback Callback functor to be called on non critical
+    ///                               issues.
+    ZoneCheckerCallbacks(const IssueCallback& error_callback,
+                         const IssueCallback& warn_callback) :
+        error_callback_(error_callback), warn_callback_(warn_callback)
+    {}
+
+    /// \brief Call the callback for a critical error.
+    ///
+    /// This method itself is exception free, but propagates any exception
+    /// thrown from the callback.
+    ///
+    /// \param reason Textual representation of the reason for the error.
+    void error(const std::string& reason) const {
+        if (!error_callback_.empty()) {
+            error_callback_(reason);
+        }
+    }
+
+    /// \brief Call the callback for a non critical issue.
+    ///
+    /// This method itself is exception free, but propagates any exception
+    /// thrown from the callback.
+    ///
+    /// \param reason Textual representation of the reason for the issue.
+    void warn(const std::string& reason) const {
+        if (!warn_callback_.empty())
+            warn_callback_(reason);
+    }
+
+private:
+    IssueCallback error_callback_;
+    IssueCallback warn_callback_;
+};
+
+/// \brief Perform basic integrity checks on zone RRsets.
+///
+/// This function performs some lightweight checks on zone's SOA and (apex)
+/// NS records.  Here, lightweight means it doesn't require traversing
+/// the entire zone, and should be expected to complete reasonably quickly
+/// regardless of the size of the zone.
+///
+/// It distinguishes "critical" errors and other undesirable issues:
+/// the former should be interpreted as the resulting zone shouldn't be used
+/// further, e.g, by an authoritative server implementation; the latter means
+/// the issues are better to be addressed but are not necessarily considered
+/// to make the zone invalid.  Critical errors are reported via the
+/// \c error() method of \c callbacks, and non critical issues are reported
+/// via its \c warn() method.
+///
+/// Specific checks performed by this function is as follows.  Failure of
+/// a check is considered a critical error unless noted otherwise:
+/// - There is exactly one SOA RR at the zone apex.
+/// - There is at least one NS RR at the zone apex.
+/// - For each apex NS record, if the NS name (the RDATA of the record) is
+///   in the zone (i.e., it's a subdomain of the zone origin and above any
+///   zone cut due to delegation), check the following:
+///   - the NS name should have an address record (AAAA or A).  Failure of
+///     this check is considered a non critical issue.
+///   - the NS name does not have a CNAME.  This is prohibited by Section
+///     10.3 of RFC 2181.
+///   - the NS name is not subject to DNAME substitution.  This is prohibited
+///     by Section 4 of RFC 6672.
+///   .
+///
+/// In addition, when the check is completed without any critical error, this
+/// function guarantees that RRsets for the SOA and (apex) NS stored in the
+/// passed RRset collection have the expected type of Rdata objects,
+/// i.e., generic::SOA and generic::NS, respectively.  (This is normally
+/// expected to be the case, but not guaranteed by the API).
+///
+/// As for the check on the existence of AAAA or A records for NS names,
+/// it should be noted that BIND 9 treats this as a critical error.
+/// It's not clear whether it's an implementation dependent behavior or
+/// based on the protocol standard (it looks like the former), but to make
+/// it sure we need to confirm there is even no wildcard match for the names.
+/// This should be a very rare configuration, and more expensive to detect,
+/// so we do not check this condition, and treat this case as a non critical
+/// issue.
+///
+/// This function indicates the result of the checks (whether there is a
+/// critical error) via the return value: It returns \c true if there is no
+/// critical error and returns \c false otherwise.  It doesn't throw an
+/// exception on encountering an error so that it can report as many errors
+/// as possible in a single call.  If an exception is a better way to signal
+/// the error, the caller can pass a callback object that throws from its
+/// \c error() method.
+///
+/// This function can still throw an exception if it finds a really bogus
+/// condition that is most likely to be an implementation bug of the caller.
+/// Such cases include when an RRset contained in the RRset collection is
+/// empty.
+///
+/// \throw Unexpected Conditions that suggest a caller's bug (see the
+/// description)
+///
+/// \param zone_name The name of the zone to be checked
+/// \param zone_class The RR class of the zone to be checked
+/// \param zone_rrsets The collection of RRsets of the zone
+/// \param callbacks Callback object used to report errors and issues
+///
+/// \return \c true if no critical errors are found; \c false otherwise.
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+          const RRsetCollectionBase& zone_rrsets,
+          const ZoneCheckerCallbacks& callbacks);
+
+} // namespace dns
+} // namespace isc
+#endif  // ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/log/log_messages.mes b/src/lib/log/log_messages.mes
index f150f39..4bc8c98 100644
--- a/src/lib/log/log_messages.mes
+++ b/src/lib/log/log_messages.mes
@@ -97,7 +97,7 @@ There may be several reasons why this message may appear:
 - The message ID has been mis-spelled in the local message file.
 
 - The program outputting the message may not use that particular message
-(e.g. it originates in a module not used by the program.)
+(e.g. it originates in a module not used by the program).
 
 - The local file was written for an earlier version of the BIND 10 software
 and the later version no longer generates that message.
@@ -131,7 +131,7 @@ version of BIND 10.
 % LOG_READING_LOCAL_FILE reading local message file %1
 This is an informational message output by BIND 10 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.)
+one or more messages; the ID of the message will not be changed though.)
 
 % LOG_READ_ERROR error reading from message file %1: %2
 The specified error was encountered reading from the named message file.
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index 6862d0c..711eae9 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <config.h>
+
 #include <algorithm>
 #include <iostream>
 
@@ -21,6 +23,7 @@
 #include <log4cplus/consoleappender.h>
 #include <log4cplus/fileappender.h>
 #include <log4cplus/syslogappender.h>
+#include <log4cplus/helpers/loglog.h>
 
 #include <log/logger.h>
 #include <log/logger_support.h>
@@ -196,6 +199,13 @@ void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
 {
     log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
 
+    // Disable log4cplus' own logging, unless --enable-debug was
+    // specified to configure. Note that this does not change
+    // LogLog's levels (that is still just INFO).
+#ifndef ENABLE_DEBUG
+    log4cplus::helpers::LogLog::getLogLog()->setQuietMode(true);
+#endif
+
     // Set the log4cplus root to not output anything - effectively we are
     // ignoring it.
     log4cplus::Logger::getRoot().setLogLevel(log4cplus::OFF_LOG_LEVEL);
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 1f7006c..febeb10 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -177,8 +177,14 @@ class BaseComponent:
             self._start_internal()
         except Exception as e:
             logger.error(BIND10_COMPONENT_START_EXCEPTION, self.name(), e)
-            self.failed(None)
-            raise
+            try:
+                self.failed(None)
+            finally:
+                # Even failed() can fail if this happens during initial startup
+                # time.  In that case we'd rather propagate the original reason
+                # for the failure than the fact that failed() failed.  So we
+                # always re-raise the original exception.
+                raise e
 
     def stop(self):
         """
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index 688ccf5..dcd9b64 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -17,11 +17,7 @@ from isc.bind10.component import Component, BaseComponent
 import isc.bind10.sockcreator
 from bind10_config import LIBEXECPATH
 import os
-import posix
 import isc.log
-from isc.log_messages.bind10_messages import *
-
-logger = isc.log.Logger("boss")
 
 class SockCreator(BaseComponent):
     """
@@ -36,8 +32,6 @@ class SockCreator(BaseComponent):
     def __init__(self, process, boss, kind, address=None, params=None):
         BaseComponent.__init__(self, boss, kind)
         self.__creator = None
-        self.__uid = boss.uid
-        self.__gid = boss.gid
 
     def _start_internal(self):
         self._boss.curproc = 'b10-sockcreator'
@@ -46,12 +40,9 @@ class SockCreator(BaseComponent):
         self._boss.register_process(self.pid(), self)
         self._boss.set_creator(self.__creator)
         self._boss.log_started(self.pid())
-        if self.__gid is not None:
-            logger.info(BIND10_SETGID, self.__gid)
-            posix.setgid(self.__gid)
-        if self.__uid is not None:
-            logger.info(BIND10_SETUID, self.__uid)
-            posix.setuid(self.__uid)
+
+        # We are now ready for switching user.
+        self._boss.change_user()
 
     def _stop_internal(self):
         self.__creator.terminate()
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index 18efea7..8603201 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -104,10 +104,10 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.__stop_process_params = None
         self.__start_simple_params = None
         # Pretending to be boss
-        self.gid = None
-        self.__gid_set = None
-        self.uid = None
-        self.__uid_set = None
+        self.__change_user_called = False
+
+    def change_user(self):
+        self.__change_user_called = True # just record the fact it's called
 
     def __start(self):
         """
@@ -624,12 +624,6 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertTrue(process.killed)
         self.assertFalse(process.terminated)
 
-    def setgid(self, gid):
-        self.__gid_set = gid
-
-    def setuid(self, uid):
-        self.__uid_set = uid
-
     class FakeCreator:
         def pid(self):
             return 42
@@ -655,35 +649,19 @@ class ComponentTests(BossUtils, unittest.TestCase):
         """
         component = isc.bind10.special_component.SockCreator(None, self,
                                                              'needed', None)
-        orig_setgid = isc.bind10.special_component.posix.setgid
-        orig_setuid = isc.bind10.special_component.posix.setuid
-        isc.bind10.special_component.posix.setgid = self.setgid
-        isc.bind10.special_component.posix.setuid = self.setuid
         orig_creator = \
             isc.bind10.special_component.isc.bind10.sockcreator.Creator
         # Just ignore the creator call
         isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
             lambda path: self.FakeCreator()
         component.start()
-        # No gid/uid set in boss, nothing called.
-        self.assertIsNone(self.__gid_set)
-        self.assertIsNone(self.__uid_set)
+        self.assertTrue(self.__change_user_called)
         # Doesn't do anything, but doesn't crash
         component.stop()
         component.kill()
         component.kill(True)
-        self.gid = 4200
-        self.uid = 42
         component = isc.bind10.special_component.SockCreator(None, self,
                                                              'needed', None)
-        component.start()
-        # This time, it get's called
-        self.assertEqual(4200, self.__gid_set)
-        self.assertEqual(42, self.__uid_set)
-        isc.bind10.special_component.posix.setgid = orig_setgid
-        isc.bind10.special_component.posix.setuid = orig_setuid
-        isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
-            orig_creator
 
 class TestComponent(BaseComponent):
     """
diff --git a/src/lib/python/isc/datasrc/tests/.gitignore b/src/lib/python/isc/datasrc/tests/.gitignore
index 58ea8cd..0d94a86 100644
--- a/src/lib/python/isc/datasrc/tests/.gitignore
+++ b/src/lib/python/isc/datasrc/tests/.gitignore
@@ -1 +1,2 @@
 /*.sqlite3.copied
+/zoneloadertest.sqlite3
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index 7e34e70..406151c 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -122,7 +122,7 @@ as the class in the Zone Section, ANY, or NONE.
 A FORMERR response is sent back to the client.
 
 % LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
-An error occured while committing the DDNS update changes to the
+An error occurred while committing the DDNS update changes to the
 datasource. The specific error is printed. A SERVFAIL response is sent
 back to the client.
 
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 2e86ba3..97ff6e6 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -17,6 +17,7 @@ EXTRA_DIST += libxfrin_messages.py
 EXTRA_DIST += loadzone_messages.py
 EXTRA_DIST += server_common_messages.py
 EXTRA_DIST += dbutil_messages.py
+EXTRA_DIST += msgq_messages.py
 
 CLEANFILES = __init__.pyc
 CLEANFILES += bind10_messages.pyc
@@ -35,6 +36,7 @@ CLEANFILES += libxfrin_messages.pyc
 CLEANFILES += loadzone_messages.pyc
 CLEANFILES += server_common_messages.pyc
 CLEANFILES += dbutil_messages.pyc
+CLEANFILES += msgq_messages.pyc
 
 CLEANDIRS = __pycache__
 
diff --git a/src/lib/python/isc/log_messages/msgq_messages.py b/src/lib/python/isc/log_messages/msgq_messages.py
new file mode 100644
index 0000000..81efa41
--- /dev/null
+++ b/src/lib/python/isc/log_messages/msgq_messages.py
@@ -0,0 +1 @@
+from work.msgq_messages import *
diff --git a/src/lib/resolve/resolve_messages.mes b/src/lib/resolve/resolve_messages.mes
index 1df544a..6447082 100644
--- a/src/lib/resolve/resolve_messages.mes
+++ b/src/lib/resolve/resolve_messages.mes
@@ -86,7 +86,7 @@ SERVFAIL will be returned.
 
 % RESLIB_NOTSINGLE_RESPONSE response to query for <%1> was not a response
 A debug message, the response to the specified query from an upstream
-nameserver was a CNAME that had mutiple RRs in the RRset.  This is
+nameserver was a CNAME that had multiple RRs in the RRset.  This is
 an invalid response according to the standards so a SERVFAIL will be
 returned to the system making the original query.
 
@@ -138,7 +138,7 @@ A debug message, the response to the specified query indicated an error
 that is not covered by a specific code path.  A SERVFAIL will be returned.
 
 % RESLIB_RECQ_CACHE_FIND found <%1> in the cache (resolve() instance %2)
-This is a debug message and indicates that a RecursiveQuery object found the
+This is a debug message and indicates that a RecursiveQuery object found
 the specified <name, class, type> tuple in the cache.  The instance number
 at the end of the message indicates which of the two resolve() methods has
 been called.
@@ -178,7 +178,7 @@ A debug message giving the round-trip time of the last query and response.
 This is a debug message and indicates that a RunningQuery object found
 the specified <name, class, type> tuple in the cache.
 
-% RESLIB_RUNQ_CACHE_LOOKUP looking up up <%1> in the cache
+% RESLIB_RUNQ_CACHE_LOOKUP looking up <%1> in the cache
 This is a debug message and indicates that a RunningQuery object has made
 a call to its doLookup() method to look up the specified <name, class, type>
 tuple, the first action of which will be to examine the cache.
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 0e4efa5..22ce0f3 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -17,11 +17,11 @@ $NAMESPACE isc::server_common
 # \brief Messages for the server_common library
 
 % SOCKETREQUESTOR_CREATED Socket requestor created for application %1
-Debug message.  A socket requesor (client of the socket creator) is created
+Debug message.  A socket requestor (client of the socket creator) is created
 for the corresponding application.  Normally this should happen at most
 one time throughout the lifetime of the application.
 
-% SOCKETREQUESTOR_DESTROYED Socket requestor destoryed
+% SOCKETREQUESTOR_DESTROYED Socket requestor destroyed
 Debug message.  The socket requestor created at SOCKETREQUESTOR_CREATED
 has been destroyed.  This event is generally unexpected other than in
 test cases.
diff --git a/src/lib/testutils/dnsmessage_test.h b/src/lib/testutils/dnsmessage_test.h
index 262d177..6de01ec 100644
--- a/src/lib/testutils/dnsmessage_test.h
+++ b/src/lib/testutils/dnsmessage_test.h
@@ -208,10 +208,9 @@ pullSigs(std::vector<isc::dns::ConstRRsetPtr>& rrsets,
 {
     for (ITERATOR it = begin; it != end; ++it) {
         rrsets.push_back(*it);
-        text += (*it)->toText();
+        text += (*it)->toText(); // this will include RRSIG, if attached.
         if ((*it)->getRRsig()) {
             rrsets.push_back((*it)->getRRsig());
-            text += (*it)->getRRsig()->toText();
         }
     }
 }
diff --git a/src/lib/util/encode/base_n.cc b/src/lib/util/encode/base_n.cc
index 0026a0b..c38f901 100644
--- a/src/lib/util/encode/base_n.cc
+++ b/src/lib/util/encode/base_n.cc
@@ -291,7 +291,8 @@ BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::decode(
                 isc_throw(BadValue, "Too many " << algorithm
                           << " padding characters: " << input);
             }
-        } else if (ch < 0 || !isspace(ch)) {
+        } else if (!(ch > 0 && isspace(ch))) {
+            // see the note for DecodeNormalizer::skipSpaces() above for ch > 0
             break;
         }
         ++srit;
diff --git a/tests/system/bindctl/setup.sh b/tests/system/bindctl/setup.sh
index 31b1016..7a9a323 100755
--- a/tests/system/bindctl/setup.sh
+++ b/tests/system/bindctl/setup.sh
@@ -22,5 +22,5 @@ SUBTEST_TOP=${TEST_TOP}/bindctl
 cp ${SUBTEST_TOP}/nsx1/b10-config.db.template ${SUBTEST_TOP}/nsx1/b10-config.db
 
 rm -f ${SUBTEST_TOP}/*/zone.sqlite3
-${B10_LOADZONE} -c '{"database_file": "'${SUBTEST_TOP}/nsx1/zone.sqlite3'"}' \
+${B10_LOADZONE} -i 1 -c "{\"database_file\": \"${SUBTEST_TOP}/nsx1/zone.sqlite3\"}" \
 	. ${SUBTEST_TOP}//nsx1/root.db
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
index 9611c90..75c91de 100755
--- a/tests/system/bindctl/tests.sh
+++ b/tests/system/bindctl/tests.sh
@@ -85,6 +85,7 @@ config commit
 quit
 ' | $RUN_BINDCTL \
 	--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+sleep 2
 $DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
 # perform a simple check on the output (digcomp would be too much for this)
 grep 192.0.2.1 dig.out.$n > /dev/null || status=1
@@ -118,6 +119,7 @@ config commit
 quit
 " | $RUN_BINDCTL \
 	--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+sleep 2
 $DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
 grep 192.0.2.2 dig.out.$n > /dev/null || status=1
 if [ $status != 0 ]; then echo "I:failed"; fi
@@ -148,6 +150,7 @@ quit
 ' | $RUN_BINDCTL \
 	--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
 done
+sleep 2
 $DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
 grep 192.0.2.2 dig.out.$n > /dev/null || status=1
 if [ $status != 0 ]; then echo "I:failed"; fi
@@ -183,6 +186,7 @@ quit
 ' | $RUN_BINDCTL \
 	--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
 done
+sleep 2
 $DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
 grep 192.0.2.2 dig.out.$n > /dev/null || status=1
 if [ $status != 0 ]; then echo "I:failed"; fi
diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in
index 92f72fa..4948d63 100755
--- a/tests/system/conf.sh.in
+++ b/tests/system/conf.sh.in
@@ -51,7 +51,10 @@ export RNDC=$BIND9_TOP/bin/rndc/rndc
 export TESTSOCK=$BIND9_TOP/bin/tests/system/testsock.pl
 export DIGCOMP=$BIND9_TOP/bin/tests/system/digcomp.pl
 
-export SUBDIRS="bindctl glue ixfr/in-2"
+# bindctl test doesn't work right now and is disabled (see #2568)
+#export SUBDIRS="bindctl glue ixfr/in-2"
+export SUBDIRS="glue ixfr/in-2"
+
 # Add appropriate subdirectories to the above statement as the tests become
 # available.
 #SUBDIRS="dnssec masterfile ixfr/in-1 ixfr/in-2 ixfr/in-4"
diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in
index dc5b28a..2f9f886 100755
--- a/tests/system/glue/setup.sh.in
+++ b/tests/system/glue/setup.sh.in
@@ -18,8 +18,8 @@ SYSTEMTESTTOP=..
 . $SYSTEMTESTTOP/conf.sh
 
 rm -f */zone.sqlite3
-${B10_LOADZONE} -o . -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/root.db
-${B10_LOADZONE} -o root-servers.nil -d @builddir@/nsx1/zone.sqlite3 \
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' . @builddir@/nsx1/root.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' root-servers.nil \
 	@builddir@/nsx1/root-servers.nil.db
-${B10_LOADZONE} -o com -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/com.db
-${B10_LOADZONE} -o net -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/net.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' com @builddir@/nsx1/com.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' net @builddir@/nsx1/net.db
diff --git a/tests/system/ixfr/in-1/setup.sh.in b/tests/system/ixfr/in-1/setup.sh.in
index d4c3978..4332930 100644
--- a/tests/system/ixfr/in-1/setup.sh.in
+++ b/tests/system/ixfr/in-1/setup.sh.in
@@ -27,4 +27,4 @@ cp -f $IXFR_TOP/db.example.n4 ns1/db.example
 
 # Set up the IXFR client - load the same version of the zone.
 cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n4
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n4
diff --git a/tests/system/ixfr/in-2/setup.sh.in b/tests/system/ixfr/in-2/setup.sh.in
index a5f64e5..a210636 100644
--- a/tests/system/ixfr/in-2/setup.sh.in
+++ b/tests/system/ixfr/in-2/setup.sh.in
@@ -26,4 +26,4 @@ cp -f $IXFR_TOP/db.example.n6 ns1/db.example
 
 # Set up the IXFR client - load an earlier version of the zone
 cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n6
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n6
diff --git a/tests/system/ixfr/in-3/setup.sh.in b/tests/system/ixfr/in-3/setup.sh.in
index 867e06e..2a08c58 100644
--- a/tests/system/ixfr/in-3/setup.sh.in
+++ b/tests/system/ixfr/in-3/setup.sh.in
@@ -26,4 +26,4 @@ cp -f $IXFR_TOP/db.example.n0 ns1/db.example
 
 # Set up the IXFR client - load a previous version of the zone.
 cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n2
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2
diff --git a/tests/system/ixfr/in-4/setup.sh.in b/tests/system/ixfr/in-4/setup.sh.in
index 7419e27..1c2e9c8 100644
--- a/tests/system/ixfr/in-4/setup.sh.in
+++ b/tests/system/ixfr/in-4/setup.sh.in
@@ -27,4 +27,4 @@ cp -f $IXFR_TOP/db.example.n2.refresh ns1/db.example
 # Set up the IXFR client - load a previous version of the zone with a short
 # refresh time.
 cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n2.refresh
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2.refresh
diff --git a/tests/tools/perfdhcp/tests/.gitignore b/tests/tools/perfdhcp/tests/.gitignore
index d6d1ec8..5697f95 100644
--- a/tests/tools/perfdhcp/tests/.gitignore
+++ b/tests/tools/perfdhcp/tests/.gitignore
@@ -1 +1,6 @@
 /run_unittests
+/test1.hex
+/test2.hex
+/test3.hex
+/test4.hex
+/test5.hex



More information about the bind10-changes mailing list